From ed529f14117847bb508d485015827ca55a47cdd9 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 7 Dec 2022 15:12:00 -0500 Subject: [PATCH 01/14] 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 --- qiskit/transpiler/passes/layout/csp_layout.py | 14 +++++++- .../passes/layout/full_ancilla_allocation.py | 10 ++++-- .../passes/layout/layout_2q_distance.py | 9 ++++- .../passes/layout/noise_adaptive_layout.py | 9 ++++- .../transpiler/passes/layout/sabre_layout.py | 14 +++++++- .../passes/layout/trivial_layout.py | 16 ++++++--- .../transpiler/passes/routing/basic_swap.py | 8 ++++- .../transpiler/passes/routing/bip_mapping.py | 16 +++++++-- .../passes/routing/layout_transformation.py | 15 +++++--- .../passes/routing/lookahead_swap.py | 8 ++++- .../transpiler/passes/routing/sabre_swap.py | 17 +++++++--- .../passes/routing/stochastic_swap.py | 10 +++++- qiskit/transpiler/passes/utils/check_map.py | 8 ++++- .../preset_passmanagers/builtin_plugins.py | 34 +++++++++++++------ .../transpiler/preset_passmanagers/common.py | 10 ++++-- .../transpiler/preset_passmanagers/level0.py | 8 ++--- .../transpiler/preset_passmanagers/level1.py | 14 ++++---- .../transpiler/preset_passmanagers/level2.py | 8 ++--- .../transpiler/preset_passmanagers/level3.py | 8 ++--- ...aware-layout-routing-2b39bd87a9f928e7.yaml | 34 +++++++++++++++++++ 20 files changed, 211 insertions(+), 59 deletions(-) create mode 100644 releasenotes/notes/target-aware-layout-routing-2b39bd87a9f928e7.yaml diff --git a/qiskit/transpiler/passes/layout/csp_layout.py b/qiskit/transpiler/passes/layout/csp_layout.py index 704c91c4de46..357c3f019623 100644 --- a/qiskit/transpiler/passes/layout/csp_layout.py +++ b/qiskit/transpiler/passes/layout/csp_layout.py @@ -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. @@ -50,6 +56,9 @@ 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 @@ -57,6 +66,9 @@ def __init__( 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""" diff --git a/qiskit/transpiler/passes/layout/full_ancilla_allocation.py b/qiskit/transpiler/passes/layout/full_ancilla_allocation.py index 4c678af5ea78..56d81141dd78 100644 --- a/qiskit/transpiler/passes/layout/full_ancilla_allocation.py +++ b/qiskit/transpiler/passes/layout/full_ancilla_allocation.py @@ -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. 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`. diff --git a/qiskit/transpiler/passes/layout/layout_2q_distance.py b/qiskit/transpiler/passes/layout/layout_2q_distance.py index f31b44591f0c..39534512585b 100644 --- a/qiskit/transpiler/passes/layout/layout_2q_distance.py +++ b/qiskit/transpiler/passes/layout/layout_2q_distance.py @@ -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): """ diff --git a/qiskit/transpiler/passes/layout/noise_adaptive_layout.py b/qiskit/transpiler/passes/layout/noise_adaptive_layout.py index f8dcc566d1be..773cc98992b1 100644 --- a/qiskit/transpiler/passes/layout/noise_adaptive_layout.py +++ b/qiskit/transpiler/passes/layout/noise_adaptive_layout.py @@ -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): @@ -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 @@ -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.""" diff --git a/qiskit/transpiler/passes/layout/sabre_layout.py b/qiskit/transpiler/passes/layout/sabre_layout.py index 3c3abdaf3dce..21022b77556a 100644 --- a/qiskit/transpiler/passes/layout/sabre_layout.py +++ b/qiskit/transpiler/passes/layout/sabre_layout.py @@ -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. @@ -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 @@ -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`. diff --git a/qiskit/transpiler/passes/layout/trivial_layout.py b/qiskit/transpiler/passes/layout/trivial_layout.py index 343c5d09e68c..865d0d2e8019 100644 --- a/qiskit/transpiler/passes/layout/trivial_layout.py +++ b/qiskit/transpiler/passes/layout/trivial_layout.py @@ -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`. @@ -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())) ) diff --git a/qiskit/transpiler/passes/routing/basic_swap.py b/qiskit/transpiler/passes/routing/basic_swap.py index 5eb7b4a834eb..b2106d0d3da5 100644 --- a/qiskit/transpiler/passes/routing/basic_swap.py +++ b/qiskit/transpiler/passes/routing/basic_swap.py @@ -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`. diff --git a/qiskit/transpiler/passes/routing/bip_mapping.py b/qiskit/transpiler/passes/routing/bip_mapping.py index 7a87b8272e40..c4e4afda8a24 100644 --- a/qiskit/transpiler/passes/routing/bip_mapping.py +++ b/qiskit/transpiler/passes/routing/bip_mapping.py @@ -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__) @@ -64,7 +65,7 @@ class BIPMapping(TransformationPass): def __init__( self, - coupling_map, + coupling_map=None, qubit_subset=None, objective="balanced", backend_prop=None, @@ -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. @@ -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. @@ -114,8 +119,6 @@ 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 @@ -123,6 +126,13 @@ def __init__( 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 diff --git a/qiskit/transpiler/passes/routing/layout_transformation.py b/qiskit/transpiler/passes/routing/layout_transformation.py index 3898d2f8256c..09fdc4c0e97f 100644 --- a/qiskit/transpiler/passes/routing/layout_transformation.py +++ b/qiskit/transpiler/passes/routing/layout_transformation.py @@ -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. @@ -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 diff --git a/qiskit/transpiler/passes/routing/lookahead_swap.py b/qiskit/transpiler/passes/routing/lookahead_swap.py index c616169485d0..5640ee40aca3 100644 --- a/qiskit/transpiler/passes/routing/lookahead_swap.py +++ b/qiskit/transpiler/passes/routing/lookahead_swap.py @@ -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: @@ -90,6 +90,9 @@ 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__() @@ -97,6 +100,9 @@ def __init__(self, coupling_map, search_depth=4, search_width=4, fake_run=False) 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`. diff --git a/qiskit/transpiler/passes/routing/sabre_swap.py b/qiskit/transpiler/passes/routing/sabre_swap.py index 374967fec01a..5a8e76a38858 100644 --- a/qiskit/transpiler/passes/routing/sabre_swap.py +++ b/qiskit/transpiler/passes/routing/sabre_swap.py @@ -72,7 +72,9 @@ class SabreSwap(TransformationPass): `arXiv:1809.02573 `_ """ - 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: @@ -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. @@ -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: diff --git a/qiskit/transpiler/passes/routing/stochastic_swap.py b/qiskit/transpiler/passes/routing/stochastic_swap.py index 1882382b8c72..cb3dae7bb7ea 100644 --- a/qiskit/transpiler/passes/routing/stochastic_swap.py +++ b/qiskit/transpiler/passes/routing/stochastic_swap.py @@ -48,7 +48,9 @@ class StochasticSwap(TransformationPass): the circuit. """ - def __init__(self, coupling_map, trials=20, seed=None, fake_run=False, initial_layout=None): + def __init__( + self, coupling_map, trials=20, seed=None, fake_run=False, initial_layout=None, target=None + ): """StochasticSwap initializer. The coupling map is a connected graph @@ -63,6 +65,9 @@ def __init__(self, coupling_map, trials=20, seed=None, fake_run=False, initial_l fake_run (bool): if true, it only pretend to do routing, i.e., no swap is effectively added. initial_layout (Layout): starting layout at beginning of pass. + 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 @@ -74,6 +79,9 @@ def __init__(self, coupling_map, trials=20, seed=None, fake_run=False, initial_l self.initial_layout = initial_layout self._qubit_to_int = None self._int_to_qubit = None + self.target = target + if self.target is not None: + self.coupling_map = self.target.build_coupling_map() def run(self, dag): """Run the StochasticSwap pass on `dag`. diff --git a/qiskit/transpiler/passes/utils/check_map.py b/qiskit/transpiler/passes/utils/check_map.py index 0fc635b32094..306431912a3d 100644 --- a/qiskit/transpiler/passes/utils/check_map.py +++ b/qiskit/transpiler/passes/utils/check_map.py @@ -24,14 +24,20 @@ class CheckMap(AnalysisPass): property ``is_swap_mapped`` to ``True`` or ``False`` accordingly. """ - def __init__(self, coupling_map): + def __init__(self, coupling_map, target=None): """CheckMap initializer. Args: coupling_map (CouplingMap): 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.target = target + if self.target is not None: + self.coupling_map = self.target.build_coupling_map() def run(self, dag): """Run the CheckMap pass on `dag`. diff --git a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py index ba4c1266ce68..83ccd86e9a75 100644 --- a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py +++ b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py @@ -32,7 +32,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana target = pass_manager_config.target coupling_map = pass_manager_config.coupling_map backend_properties = pass_manager_config.backend_properties - routing_pass = BasicSwap(coupling_map) + routing_pass = BasicSwap(coupling_map, target=target) vf2_call_limit = common.get_vf2_call_limit( optimization_level, pass_manager_config.layout_method, @@ -95,9 +95,13 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana pass_manager_config.initial_layout, ) if optimization_level == 3: - routing_pass = StochasticSwap(coupling_map, trials=200, seed=seed_transpiler) + routing_pass = StochasticSwap( + coupling_map, trials=200, seed=seed_transpiler, target=target + ) else: - routing_pass = StochasticSwap(coupling_map, trials=20, seed=seed_transpiler) + routing_pass = StochasticSwap( + coupling_map, trials=20, seed=seed_transpiler, target=target + ) if optimization_level == 0: return common.generate_routing_passmanager( @@ -146,7 +150,9 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana pass_manager_config.initial_layout, ) if optimization_level == 0: - routing_pass = LookaheadSwap(coupling_map, search_depth=2, search_width=2) + routing_pass = LookaheadSwap( + coupling_map, search_depth=2, search_width=2, target=target + ) return common.generate_routing_passmanager( routing_pass, target, @@ -155,7 +161,9 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana use_barrier_before_measurement=True, ) if optimization_level == 1: - routing_pass = LookaheadSwap(coupling_map, search_depth=4, search_width=4) + routing_pass = LookaheadSwap( + coupling_map, search_depth=4, search_width=4, target=target + ) return common.generate_routing_passmanager( routing_pass, target, @@ -167,7 +175,9 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana use_barrier_before_measurement=True, ) if optimization_level == 2: - routing_pass = LookaheadSwap(coupling_map, search_depth=5, search_width=6) + routing_pass = LookaheadSwap( + coupling_map, search_depth=5, search_width=6, target=target + ) return common.generate_routing_passmanager( routing_pass, target, @@ -178,7 +188,9 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana use_barrier_before_measurement=True, ) if optimization_level == 3: - routing_pass = LookaheadSwap(coupling_map, search_depth=5, search_width=6) + routing_pass = LookaheadSwap( + coupling_map, search_depth=5, search_width=6, target=target + ) return common.generate_routing_passmanager( routing_pass, target, @@ -207,7 +219,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana ) if optimization_level == 0: routing_pass = SabreSwap( - coupling_map, heuristic="basic", seed=seed_transpiler, trials=5 + coupling_map, heuristic="basic", seed=seed_transpiler, trials=5, target=target ) return common.generate_routing_passmanager( routing_pass, @@ -218,7 +230,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana ) if optimization_level == 1: routing_pass = SabreSwap( - coupling_map, heuristic="decay", seed=seed_transpiler, trials=5 + coupling_map, heuristic="decay", seed=seed_transpiler, trials=5, target=target ) return common.generate_routing_passmanager( routing_pass, @@ -232,7 +244,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana ) if optimization_level == 2: routing_pass = SabreSwap( - coupling_map, heuristic="decay", seed=seed_transpiler, trials=10 + coupling_map, heuristic="decay", seed=seed_transpiler, trials=10, target=target ) return common.generate_routing_passmanager( routing_pass, @@ -245,7 +257,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana ) if optimization_level == 3: routing_pass = SabreSwap( - coupling_map, heuristic="decay", seed=seed_transpiler, trials=20 + coupling_map, heuristic="decay", seed=seed_transpiler, trials=20, target=target ) return common.generate_routing_passmanager( routing_pass, diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index 8766b526ee72..fd0aa68af145 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -196,7 +196,7 @@ def generate_unroll_3q( return unroll_3q -def generate_embed_passmanager(coupling_map): +def generate_embed_passmanager(coupling_map=None, target=None): """Generate a layout embedding :class:`~qiskit.transpiler.PassManager` This is used to generate a :class:`~qiskit.transpiler.PassManager` object @@ -205,11 +205,15 @@ def generate_embed_passmanager(coupling_map): Args: coupling_map (CouplingMap): The coupling map for the backend to embed the circuit to. + target (Target): The target for the compiler to embed the circuit to. + If specified this will superscede the provided ``coupling_map`` Returns: PassManager: The embedding passmanager that assumes the layout property set has been set in earlier stages """ - return PassManager([FullAncillaAllocation(coupling_map), EnlargeWithAncilla(), ApplyLayout()]) + return PassManager( + [FullAncillaAllocation(coupling_map, target=target), EnlargeWithAncilla(), ApplyLayout()] + ) def _layout_not_perfect(property_set): @@ -274,7 +278,7 @@ def _run_post_layout_condition(property_set): return False routing = PassManager() - routing.append(CheckMap(coupling_map)) + routing.append(CheckMap(coupling_map, target=target)) def _swap_condition(property_set): return not property_set["is_swap_mapped"] diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index 78cffd88f24a..51155a04fc49 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -78,14 +78,14 @@ def _choose_layout_condition(property_set): return not property_set["layout"] if layout_method == "trivial": - _choose_layout = TrivialLayout(coupling_map) + _choose_layout = TrivialLayout(coupling_map, target=target) elif layout_method == "dense": _choose_layout = DenseLayout(coupling_map, backend_properties, target=target) elif layout_method == "noise_adaptive": - _choose_layout = NoiseAdaptiveLayout(backend_properties) + _choose_layout = NoiseAdaptiveLayout(backend_properties, target=target) elif layout_method == "sabre": _choose_layout = SabreLayout( - coupling_map, max_iterations=1, seed=seed_transpiler, swap_trials=5 + coupling_map, max_iterations=1, seed=seed_transpiler, swap_trials=5, target=target ) # Choose routing pass @@ -112,7 +112,7 @@ def _choose_layout_condition(property_set): layout = PassManager() layout.append(_given_layout) layout.append(_choose_layout, condition=_choose_layout_condition) - layout += common.generate_embed_passmanager(coupling_map) + layout += common.generate_embed_passmanager(coupling_map, target) routing = routing_pm else: layout = None diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index e3b1cdc88735..98edad329a61 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -115,7 +115,7 @@ def _vf2_match_not_found(property_set): _choose_layout_0 = ( [] if pass_manager_config.layout_method - else [TrivialLayout(coupling_map), CheckMap(coupling_map)] + else [TrivialLayout(coupling_map, target=target), CheckMap(coupling_map, target=target)] ) _choose_layout_1 = ( @@ -131,19 +131,21 @@ def _vf2_match_not_found(property_set): ) if layout_method == "trivial": - _improve_layout = TrivialLayout(coupling_map) + _improve_layout = TrivialLayout(coupling_map, target=target) elif layout_method == "dense": _improve_layout = DenseLayout(coupling_map, backend_properties, target=target) elif layout_method == "noise_adaptive": - _improve_layout = NoiseAdaptiveLayout(backend_properties) + _improve_layout = NoiseAdaptiveLayout(backend_properties, target=target) elif layout_method == "sabre": _improve_layout = SabreLayout( - coupling_map, max_iterations=2, seed=seed_transpiler, swap_trials=5 + coupling_map, max_iterations=2, seed=seed_transpiler, swap_trials=5, target=target ) elif layout_method is None: _improve_layout = common.if_has_control_flow_else( DenseLayout(coupling_map, backend_properties, target=target), - SabreLayout(coupling_map, max_iterations=2, seed=seed_transpiler, swap_trials=5), + SabreLayout( + coupling_map, max_iterations=2, seed=seed_transpiler, swap_trials=5, target=target + ), ).to_flow_controller() # Choose routing pass @@ -201,7 +203,7 @@ def _opt_control(property_set): layout.append(_choose_layout_0, condition=_choose_layout_condition) layout.append(_choose_layout_1, condition=_layout_not_perfect) layout.append(_improve_layout, condition=_vf2_match_not_found) - layout += common.generate_embed_passmanager(coupling_map) + layout += common.generate_embed_passmanager(coupling_map, target=target) routing = routing_pm diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index adbb75d86b55..22675b28473f 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -121,14 +121,14 @@ def _vf2_match_not_found(property_set): ) if layout_method == "trivial": - _choose_layout_1 = TrivialLayout(coupling_map) + _choose_layout_1 = TrivialLayout(coupling_map, target=target) elif layout_method == "dense": _choose_layout_1 = DenseLayout(coupling_map, backend_properties, target=target) elif layout_method == "noise_adaptive": - _choose_layout_1 = NoiseAdaptiveLayout(backend_properties) + _choose_layout_1 = NoiseAdaptiveLayout(backend_properties, target=target) elif layout_method == "sabre": _choose_layout_1 = SabreLayout( - coupling_map, max_iterations=2, seed=seed_transpiler, swap_trials=10 + coupling_map, max_iterations=2, seed=seed_transpiler, swap_trials=10, target=target ) # Choose routing pass @@ -169,7 +169,7 @@ def _opt_control(property_set): layout.append(_given_layout) layout.append(_choose_layout_0, condition=_choose_layout_condition) layout.append(_choose_layout_1, condition=_vf2_match_not_found) - layout += common.generate_embed_passmanager(coupling_map) + layout += common.generate_embed_passmanager(coupling_map, target=target) routing = routing_pm else: layout = None diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index 5abb44fa1c81..4c22be59078d 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -127,14 +127,14 @@ def _vf2_match_not_found(property_set): ) # 2b. if VF2 didn't converge on a solution use layout_method (dense). if layout_method == "trivial": - _choose_layout_1 = TrivialLayout(coupling_map) + _choose_layout_1 = TrivialLayout(coupling_map, target=target) elif layout_method == "dense": _choose_layout_1 = DenseLayout(coupling_map, backend_properties, target=target) elif layout_method == "noise_adaptive": - _choose_layout_1 = NoiseAdaptiveLayout(backend_properties) + _choose_layout_1 = NoiseAdaptiveLayout(backend_properties, target=target) elif layout_method == "sabre": _choose_layout_1 = SabreLayout( - coupling_map, max_iterations=4, seed=seed_transpiler, swap_trials=20 + coupling_map, max_iterations=4, seed=seed_transpiler, swap_trials=20, target=target ) # Choose routing pass @@ -196,7 +196,7 @@ def _opt_control(property_set): layout.append(_given_layout) layout.append(_choose_layout_0, condition=_choose_layout_condition) layout.append(_choose_layout_1, condition=_vf2_match_not_found) - layout += common.generate_embed_passmanager(coupling_map) + layout += common.generate_embed_passmanager(coupling_map, target=target) routing = routing_pm else: layout = None diff --git a/releasenotes/notes/target-aware-layout-routing-2b39bd87a9f928e7.yaml b/releasenotes/notes/target-aware-layout-routing-2b39bd87a9f928e7.yaml new file mode 100644 index 000000000000..0fddf32a4b08 --- /dev/null +++ b/releasenotes/notes/target-aware-layout-routing-2b39bd87a9f928e7.yaml @@ -0,0 +1,34 @@ +--- +features: + - | + The following layout and routing transpiler passes from the + :mod:`.qiskit.transpiler.passes` modules have a new keyword argument, + ``target`` which takes in a :class:`~.Target` object which is used to model + the constraints of a target backend. If the ``target`` keyword argument is + specified it will be used as the source of truth for any hardware + constraints used in the operation of the transpiler pass. It will + superscede any other arguments for specifying hardware constraints, + typically those arguments which take a :class:`~.CouplingMap` or a + :class:`~.BackendProperties` object. + + The list of these passes with the new ``target`` argument are: + + * :class:`~.CSPLayout` + * :class:`~.FullAncillaAllocation` + * :class:`~.Layout2qDistance` + * :class:`~.NoiseAdaptiveLayout` + * :class:`~.SabreLayout` + * :class:`~.TrivialLayout` + * :class:`~.BasicSwap` + * :class:`~.BIPMapping` + * :class:`~.LayoutTransformation` + * :class:`~.LookaheadSwap` + * :class:`~.SabreSwap` + * :class:`~.StochasticSwap` + * :class:`~.CheckMap` + - | + The pass manager construction helper function :func:`~.generate_embed_passmanager` + has a new keyword argument ``target`` which is used to specify a :class:`~.Target` + object to model the constraints of the target backend being compiled for + when generating a new :class:~.PassManager`. If specified this new argument will + superscede the other argument ``coupling_map``. From 9a1e49b2c67646a400f407c4816723402ada7c92 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 7 Dec 2022 17:03:32 -0500 Subject: [PATCH 02/14] 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. --- .../references/pass_manager_standard.dot | 66 ++++++++++--------- .../references/pass_manager_style.dot | 66 ++++++++++--------- 2 files changed, 72 insertions(+), 60 deletions(-) diff --git a/test/python/visualization/references/pass_manager_standard.dot b/test/python/visualization/references/pass_manager_standard.dot index 0eb868f15352..8cc4a131988c 100644 --- a/test/python/visualization/references/pass_manager_standard.dot +++ b/test/python/visualization/references/pass_manager_standard.dot @@ -13,73 +13,79 @@ fontname=helvetica; label="[1] condition"; labeljust=l; 4 [color=red, fontname=helvetica, label=TrivialLayout, shape=rectangle]; -5 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid]; +5 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=dashed]; 5 -> 4; +6 [color=black, fontname=helvetica, fontsize=10, label=target, shape=ellipse, style=dashed]; +6 -> 4; 1 -> 4; } -subgraph cluster_6 { +subgraph cluster_7 { fontname=helvetica; label="[2] "; labeljust=l; -7 [color=red, fontname=helvetica, label=FullAncillaAllocation, shape=rectangle]; -8 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid]; -8 -> 7; -4 -> 7; +8 [color=red, fontname=helvetica, label=FullAncillaAllocation, shape=rectangle]; +9 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=dashed]; +9 -> 8; +10 [color=black, fontname=helvetica, fontsize=10, label=target, shape=ellipse, style=dashed]; +10 -> 8; +4 -> 8; } -subgraph cluster_9 { +subgraph cluster_11 { fontname=helvetica; label="[3] "; labeljust=l; -10 [color=blue, fontname=helvetica, label=EnlargeWithAncilla, shape=rectangle]; -7 -> 10; +12 [color=blue, fontname=helvetica, label=EnlargeWithAncilla, shape=rectangle]; +8 -> 12; } -subgraph cluster_11 { +subgraph cluster_13 { fontname=helvetica; label="[4] "; labeljust=l; -12 [color=blue, fontname=helvetica, label=Unroller, shape=rectangle]; -13 [color=black, fontname=helvetica, fontsize=10, label=basis, shape=ellipse, style=solid]; -13 -> 12; -10 -> 12; +14 [color=blue, fontname=helvetica, label=Unroller, shape=rectangle]; +15 [color=black, fontname=helvetica, fontsize=10, label=basis, shape=ellipse, style=solid]; +15 -> 14; +12 -> 14; } -subgraph cluster_14 { +subgraph cluster_16 { fontname=helvetica; label="[5] "; labeljust=l; -15 [color=red, fontname=helvetica, label=CheckMap, shape=rectangle]; -16 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid]; -16 -> 15; -12 -> 15; +17 [color=red, fontname=helvetica, label=CheckMap, shape=rectangle]; +18 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid]; +18 -> 17; +19 [color=black, fontname=helvetica, fontsize=10, label=target, shape=ellipse, style=dashed]; +19 -> 17; +14 -> 17; } -subgraph cluster_17 { +subgraph cluster_20 { fontname=helvetica; label="[6] do_while"; labeljust=l; -18 [color=blue, fontname=helvetica, label=BarrierBeforeFinalMeasurements, shape=rectangle]; -15 -> 18; +21 [color=blue, fontname=helvetica, label=BarrierBeforeFinalMeasurements, shape=rectangle]; +17 -> 21; } -subgraph cluster_19 { +subgraph cluster_22 { fontname=helvetica; label="[7] "; labeljust=l; -20 [color=blue, fontname=helvetica, label=CXDirection, shape=rectangle]; -21 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid]; -21 -> 20; -18 -> 20; +23 [color=blue, fontname=helvetica, label=CXDirection, shape=rectangle]; +24 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid]; +24 -> 23; +21 -> 23; } -subgraph cluster_22 { +subgraph cluster_25 { fontname=helvetica; label="[8] "; labeljust=l; -23 [color=blue, fontname=helvetica, label=RemoveResetInZeroState, shape=rectangle]; -20 -> 23; +26 [color=blue, fontname=helvetica, label=RemoveResetInZeroState, shape=rectangle]; +23 -> 26; } } diff --git a/test/python/visualization/references/pass_manager_style.dot b/test/python/visualization/references/pass_manager_style.dot index 136b4b0fea57..038af9573667 100644 --- a/test/python/visualization/references/pass_manager_style.dot +++ b/test/python/visualization/references/pass_manager_style.dot @@ -13,73 +13,79 @@ fontname=helvetica; label="[1] condition"; labeljust=l; 4 [color=red, fontname=helvetica, label=TrivialLayout, shape=rectangle]; -5 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid]; +5 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=dashed]; 5 -> 4; +6 [color=black, fontname=helvetica, fontsize=10, label=target, shape=ellipse, style=dashed]; +6 -> 4; 1 -> 4; } -subgraph cluster_6 { +subgraph cluster_7 { fontname=helvetica; label="[2] "; labeljust=l; -7 [color=red, fontname=helvetica, label=FullAncillaAllocation, shape=rectangle]; -8 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid]; -8 -> 7; -4 -> 7; +8 [color=red, fontname=helvetica, label=FullAncillaAllocation, shape=rectangle]; +9 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=dashed]; +9 -> 8; +10 [color=black, fontname=helvetica, fontsize=10, label=target, shape=ellipse, style=dashed]; +10 -> 8; +4 -> 8; } -subgraph cluster_9 { +subgraph cluster_11 { fontname=helvetica; label="[3] "; labeljust=l; -10 [color=pink, fontname=helvetica, label=EnlargeWithAncilla, shape=rectangle]; -7 -> 10; +12 [color=pink, fontname=helvetica, label=EnlargeWithAncilla, shape=rectangle]; +8 -> 12; } -subgraph cluster_11 { +subgraph cluster_13 { fontname=helvetica; label="[4] "; labeljust=l; -12 [color=blue, fontname=helvetica, label=Unroller, shape=rectangle]; -13 [color=black, fontname=helvetica, fontsize=10, label=basis, shape=ellipse, style=solid]; -13 -> 12; -10 -> 12; +14 [color=blue, fontname=helvetica, label=Unroller, shape=rectangle]; +15 [color=black, fontname=helvetica, fontsize=10, label=basis, shape=ellipse, style=solid]; +15 -> 14; +12 -> 14; } -subgraph cluster_14 { +subgraph cluster_16 { fontname=helvetica; label="[5] "; labeljust=l; -15 [color=green, fontname=helvetica, label=CheckMap, shape=rectangle]; -16 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid]; -16 -> 15; -12 -> 15; +17 [color=green, fontname=helvetica, label=CheckMap, shape=rectangle]; +18 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid]; +18 -> 17; +19 [color=black, fontname=helvetica, fontsize=10, label=target, shape=ellipse, style=dashed]; +19 -> 17; +14 -> 17; } -subgraph cluster_17 { +subgraph cluster_20 { fontname=helvetica; label="[6] do_while"; labeljust=l; -18 [color=blue, fontname=helvetica, label=BarrierBeforeFinalMeasurements, shape=rectangle]; -15 -> 18; +21 [color=blue, fontname=helvetica, label=BarrierBeforeFinalMeasurements, shape=rectangle]; +17 -> 21; } -subgraph cluster_19 { +subgraph cluster_22 { fontname=helvetica; label="[7] "; labeljust=l; -20 [color=blue, fontname=helvetica, label=CXDirection, shape=rectangle]; -21 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid]; -21 -> 20; -18 -> 20; +23 [color=blue, fontname=helvetica, label=CXDirection, shape=rectangle]; +24 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid]; +24 -> 23; +21 -> 23; } -subgraph cluster_22 { +subgraph cluster_25 { fontname=helvetica; label="[8] "; labeljust=l; -23 [color=grey, fontname=helvetica, label=RemoveResetInZeroState, shape=rectangle]; -20 -> 23; +26 [color=grey, fontname=helvetica, label=RemoveResetInZeroState, shape=rectangle]; +23 -> 26; } } From 5484e3c0a2ec78d4cee43f81b31795dd5b13265c Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 3 Jan 2023 10:34:17 -0500 Subject: [PATCH 03/14] Fix lint --- qiskit/transpiler/preset_passmanagers/level0.py | 2 +- qiskit/transpiler/preset_passmanagers/level3.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index d4d404cc6f64..2711684c3f9e 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -92,7 +92,7 @@ def _choose_layout_condition(property_set): swap_trials=5, layout_trials=5, skip_routing=skip_routing, - target=target + target=target, ) # Choose routing pass diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index ede9adbfb1ab..216892ff7a32 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -142,7 +142,7 @@ def _vf2_match_not_found(property_set): layout_trials=20, skip_routing=pass_manager_config.routing_method is not None and routing_method != "sabre", - target=target + target=target, ) # Choose routing pass From 8d979c9c5dd5f2a35cc182a428396ede02cf2782 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 4 Jan 2023 15:45:06 -0500 Subject: [PATCH 04/14] 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. --- qiskit/transpiler/passes/utils/check_map.py | 32 ++++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/qiskit/transpiler/passes/utils/check_map.py b/qiskit/transpiler/passes/utils/check_map.py index 306431912a3d..6b95dd366265 100644 --- a/qiskit/transpiler/passes/utils/check_map.py +++ b/qiskit/transpiler/passes/utils/check_map.py @@ -20,11 +20,14 @@ class CheckMap(AnalysisPass): """Check if a DAG circuit is already mapped to a coupling map. Check if a DAGCircuit is mapped to `coupling_map` by checking that all - 2-qubit interactions are laid out to be physically close, setting the - property ``is_swap_mapped`` to ``True`` or ``False`` accordingly. + 2-qubit interactions are laid out to be on adjacent qubits in the global coupling + map of the device, setting the property ``is_swap_mapped`` to ``True`` or ``False`` + accordingly. Note this does not validate directionality of the connectivity between + qubits. If you need to check gates are implemented in a native direction + for a target use the :class:`~.CheckGateDirection` pass instead. """ - def __init__(self, coupling_map, target=None): + def __init__(self, coupling_map=None, target=None): """CheckMap initializer. Args: @@ -34,10 +37,19 @@ def __init__(self, coupling_map, target=None): precedence and ``coupling_map`` will be ignored. """ super().__init__() - self.coupling_map = coupling_map - self.target = target - if self.target is not None: - self.coupling_map = self.target.build_coupling_map() + if coupling_map is None and target is None: + self.qargs = None + else: + self.qargs = set() + if target is not None: + for edge in target.qargs: + if len(edge) == 2: + self.qargs.add(edge) + self.qargs.add((edge[1], edge[0])) + else: + for edge in coupling_map.get_edges(): + self.qargs.add(edge) + self.qargs.add((edge[1], edge[0])) def run(self, dag): """Run the CheckMap pass on `dag`. @@ -52,12 +64,10 @@ def run(self, dag): self.property_set["is_swap_mapped"] = True - if self.coupling_map is None or len(self.coupling_map.graph) == 0: + if not self.qargs: return qubit_indices = {bit: index for index, bit in enumerate(dag.qubits)} - # Use dist matrix directly to avoid validation overhead - dist_matrix = self.coupling_map.distance_matrix for node in dag.op_nodes(include_directives=False): is_controlflow_op = isinstance(node.op, ControlFlowOp) if len(node.qargs) == 2 and not is_controlflow_op: @@ -65,7 +75,7 @@ def run(self, dag): continue physical_q0 = qubit_indices[node.qargs[0]] physical_q1 = qubit_indices[node.qargs[1]] - if dist_matrix[physical_q0, physical_q1] != 1: + if (physical_q0, physical_q1) not in self.qargs: self.property_set["check_map_msg"] = "{}({}, {}) failed".format( node.name, physical_q0, From 1f244b8a714cacf1484a390bbbc091e3e1e69e1c Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 4 Jan 2023 16:30:35 -0500 Subject: [PATCH 05/14] Fix test failures --- qiskit/transpiler/passes/utils/check_map.py | 9 +++++---- .../visualization/references/pass_manager_standard.dot | 2 +- .../visualization/references/pass_manager_style.dot | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/qiskit/transpiler/passes/utils/check_map.py b/qiskit/transpiler/passes/utils/check_map.py index 6b95dd366265..f014bdb78203 100644 --- a/qiskit/transpiler/passes/utils/check_map.py +++ b/qiskit/transpiler/passes/utils/check_map.py @@ -42,10 +42,11 @@ def __init__(self, coupling_map=None, target=None): else: self.qargs = set() if target is not None: - for edge in target.qargs: - if len(edge) == 2: - self.qargs.add(edge) - self.qargs.add((edge[1], edge[0])) + if target.qargs is not None: + for edge in target.qargs: + if len(edge) == 2: + self.qargs.add(edge) + self.qargs.add((edge[1], edge[0])) else: for edge in coupling_map.get_edges(): self.qargs.add(edge) diff --git a/test/python/visualization/references/pass_manager_standard.dot b/test/python/visualization/references/pass_manager_standard.dot index 8cc4a131988c..628f26971f24 100644 --- a/test/python/visualization/references/pass_manager_standard.dot +++ b/test/python/visualization/references/pass_manager_standard.dot @@ -55,7 +55,7 @@ fontname=helvetica; label="[5] "; labeljust=l; 17 [color=red, fontname=helvetica, label=CheckMap, shape=rectangle]; -18 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid]; +18 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=dashed]; 18 -> 17; 19 [color=black, fontname=helvetica, fontsize=10, label=target, shape=ellipse, style=dashed]; 19 -> 17; diff --git a/test/python/visualization/references/pass_manager_style.dot b/test/python/visualization/references/pass_manager_style.dot index 038af9573667..552f201710b1 100644 --- a/test/python/visualization/references/pass_manager_style.dot +++ b/test/python/visualization/references/pass_manager_style.dot @@ -55,7 +55,7 @@ fontname=helvetica; label="[5] "; labeljust=l; 17 [color=green, fontname=helvetica, label=CheckMap, shape=rectangle]; -18 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid]; +18 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=dashed]; 18 -> 17; 19 [color=black, fontname=helvetica, fontsize=10, label=target, shape=ellipse, style=dashed]; 19 -> 17; From f9b04ca95f26eb3785b461402637b3bf126ccc06 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 18 Jan 2023 16:26:05 -0500 Subject: [PATCH 06/14] Update qiskit/transpiler/passes/layout/full_ancilla_allocation.py --- qiskit/transpiler/passes/layout/full_ancilla_allocation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/layout/full_ancilla_allocation.py b/qiskit/transpiler/passes/layout/full_ancilla_allocation.py index 56d81141dd78..c13dd4d82c5c 100644 --- a/qiskit/transpiler/passes/layout/full_ancilla_allocation.py +++ b/qiskit/transpiler/passes/layout/full_ancilla_allocation.py @@ -32,7 +32,7 @@ class FullAncillaAllocation(AnalysisPass): """ def __init__(self, coupling_map=None, target=None): - """FullAncillaAllocation initializer. + """FullAncillaAllocation initializer. Args: coupling_map (Coupling): directed graph representing a coupling map. From 1ed4a68b030416809c706ac65771b2184f4dd2db Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 18 Jan 2023 17:19:07 -0500 Subject: [PATCH 07/14] Add test coverage for passes not run in preset pass managers --- .../passes/routing/layout_transformation.py | 14 ++++-- .../passes/routing/lookahead_swap.py | 4 +- test/python/transpiler/test_basic_swap.py | 37 +++++++++++++- test/python/transpiler/test_bip_mapping.py | 26 +++++++++- test/python/transpiler/test_csp_layout.py | 28 ++++++++++- test/python/transpiler/test_layout_score.py | 50 +++++++++++++++++++ .../transpiler/test_layout_transformation.py | 24 ++++++++- test/python/transpiler/test_lookahead_swap.py | 32 +++++++++++- test/python/transpiler/test_trivial_layout.py | 20 ++++++++ 9 files changed, 225 insertions(+), 10 deletions(-) diff --git a/qiskit/transpiler/passes/routing/layout_transformation.py b/qiskit/transpiler/passes/routing/layout_transformation.py index 09fdc4c0e97f..4c476770e068 100644 --- a/qiskit/transpiler/passes/routing/layout_transformation.py +++ b/qiskit/transpiler/passes/routing/layout_transformation.py @@ -30,9 +30,9 @@ class LayoutTransformation(TransformationPass): def __init__( self, - coupling_map: CouplingMap, - from_layout: Union[Layout, str], - to_layout: Union[Layout, str], + coupling_map: CouplingMap = None, + from_layout: Union[Layout, str] = None, + to_layout: Union[Layout, str] = None, seed: Union[int, np.random.default_rng] = None, trials=4, target=None, @@ -59,8 +59,16 @@ def __init__( 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: + ValueError: If requried arguments ``from_layout`` or ``to_layout`` are not + specified. """ super().__init__() + if from_layout is None: + raise TypeError("Required from_layout argument not provided.") + if to_layout is None: + raise TypeError("Required to_layout argument not provided.") self.from_layout = from_layout self.to_layout = to_layout self.coupling_map = coupling_map diff --git a/qiskit/transpiler/passes/routing/lookahead_swap.py b/qiskit/transpiler/passes/routing/lookahead_swap.py index 5640ee40aca3..ed8441b7935e 100644 --- a/qiskit/transpiler/passes/routing/lookahead_swap.py +++ b/qiskit/transpiler/passes/routing/lookahead_swap.py @@ -81,7 +81,9 @@ 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, target=None): + def __init__( + self, coupling_map=None, search_depth=4, search_width=4, fake_run=False, target=None + ): """LookaheadSwap initializer. Args: diff --git a/test/python/transpiler/test_basic_swap.py b/test/python/transpiler/test_basic_swap.py index 90f4e76aa452..777850408821 100644 --- a/test/python/transpiler/test_basic_swap.py +++ b/test/python/transpiler/test_basic_swap.py @@ -14,7 +14,8 @@ import unittest from qiskit.transpiler.passes import BasicSwap -from qiskit.transpiler import CouplingMap +from qiskit.transpiler import CouplingMap, Target +from qiskit.circuit.library import CXGate from qiskit.converters import circuit_to_dag from qiskit import QuantumRegister, QuantumCircuit from qiskit.test import QiskitTestCase @@ -105,6 +106,40 @@ def test_a_single_swap(self): self.assertEqual(circuit_to_dag(expected), after) + def test_a_single_swap_with_target(self): + """Adding a swap + q0:------- + + q1:--(+)-- + | + q2:---.--- + + CouplingMap map: [1]--[0]--[2] + + q0:--X---.--- + | | + q1:--X---|--- + | + q2:-----(+)-- + + """ + target = Target() + target.add_instruction(CXGate(), {(0, 1): None, (0, 2): None}) + + qr = QuantumRegister(3, "q") + circuit = QuantumCircuit(qr) + circuit.cx(qr[1], qr[2]) + dag = circuit_to_dag(circuit) + + expected = QuantumCircuit(qr) + expected.swap(qr[1], qr[0]) + expected.cx(qr[0], qr[2]) + + pass_ = BasicSwap(target=target) + after = pass_.run(dag) + + self.assertEqual(circuit_to_dag(expected), after) + def test_a_single_swap_bigger_cm(self): """Swapper in a bigger coupling map q0:------- diff --git a/test/python/transpiler/test_bip_mapping.py b/test/python/transpiler/test_bip_mapping.py index 24e6d5233e9c..4cd5ba46fb0f 100644 --- a/test/python/transpiler/test_bip_mapping.py +++ b/test/python/transpiler/test_bip_mapping.py @@ -16,11 +16,11 @@ from qiskit import QuantumRegister, QuantumCircuit, ClassicalRegister from qiskit.circuit import Barrier -from qiskit.circuit.library.standard_gates import SwapGate +from qiskit.circuit.library.standard_gates import SwapGate, CXGate from qiskit.converters import circuit_to_dag from qiskit.test import QiskitTestCase from qiskit.providers.fake_provider import FakeLima -from qiskit.transpiler import CouplingMap, Layout, PassManager +from qiskit.transpiler import CouplingMap, Layout, PassManager, Target from qiskit.transpiler.exceptions import TranspilerError from qiskit.transpiler.passes import BIPMapping from qiskit.transpiler.passes import CheckMap, Collect2qBlocks, ConsolidateBlocks, UnitarySynthesis @@ -127,6 +127,28 @@ def test_can_map_measurements_correctly(self): self.assertEqual(expected, actual) + def test_can_map_measurements_correctly_with_target(self): + """Verify measurement nodes are updated to map correct cregs to re-mapped qregs.""" + target = Target() + target.add_instruction(CXGate(), {(0, 1): None, (0, 2): None}) + + qr = QuantumRegister(3, "qr") + cr = ClassicalRegister(2) + circuit = QuantumCircuit(qr, cr) + circuit.cx(qr[1], qr[2]) + circuit.measure(qr[1], cr[0]) + circuit.measure(qr[2], cr[1]) + + actual = BIPMapping(target=target)(circuit) + + q = QuantumRegister(3, "q") + expected = QuantumCircuit(q, cr) + expected.cx(q[0], q[1]) + expected.measure(q[0], cr[0]) # <- changed due to initial layout change + expected.measure(q[1], cr[1]) # <- changed due to initial layout change + + self.assertEqual(expected, actual) + def test_never_modify_mapped_circuit(self): """Test that the mapping is idempotent. It should not modify a circuit which is already compatible with the diff --git a/test/python/transpiler/test_csp_layout.py b/test/python/transpiler/test_csp_layout.py index 86564c553a0d..ba04566a8119 100644 --- a/test/python/transpiler/test_csp_layout.py +++ b/test/python/transpiler/test_csp_layout.py @@ -20,7 +20,7 @@ from qiskit.transpiler.passes import CSPLayout from qiskit.converters import circuit_to_dag from qiskit.test import QiskitTestCase -from qiskit.providers.fake_provider import FakeTenerife, FakeRueschlikon, FakeTokyo +from qiskit.providers.fake_provider import FakeTenerife, FakeRueschlikon, FakeTokyo, FakeYorktownV2 class TestCSPLayout(QiskitTestCase): @@ -72,6 +72,32 @@ def test_3q_circuit_5q_coupling(self): self.assertEqual(layout[qr[2]], 4) self.assertEqual(pass_.property_set["CSPLayout_stop_reason"], "solution found") + def test_3q_circuit_5q_coupling_with_target(self): + """3 qubits in Tenerife, without considering the direction + qr1 + / | + qr0 - qr2 - 3 + | / + 4 + """ + target = FakeYorktownV2().target + + qr = QuantumRegister(3, "qr") + circuit = QuantumCircuit(qr) + circuit.cx(qr[1], qr[0]) # qr1 -> qr0 + circuit.cx(qr[0], qr[2]) # qr0 -> qr2 + circuit.cx(qr[1], qr[2]) # qr1 -> qr2 + + dag = circuit_to_dag(circuit) + pass_ = CSPLayout(target=target, strict_direction=False, seed=self.seed) + pass_.run(dag) + layout = pass_.property_set["layout"] + + self.assertEqual(layout[qr[0]], 3) + self.assertEqual(layout[qr[1]], 2) + self.assertEqual(layout[qr[2]], 4) + self.assertEqual(pass_.property_set["CSPLayout_stop_reason"], "solution found") + def test_9q_circuit_16q_coupling(self): """9 qubits in Rueschlikon, without considering the direction q0[1] - q0[0] - q1[3] - q0[3] - q1[0] - q1[1] - q1[2] - 8 diff --git a/test/python/transpiler/test_layout_score.py b/test/python/transpiler/test_layout_score.py index b1d3db9c386d..ed690fb4d863 100644 --- a/test/python/transpiler/test_layout_score.py +++ b/test/python/transpiler/test_layout_score.py @@ -15,9 +15,11 @@ import unittest from qiskit import QuantumRegister, QuantumCircuit +from qiskit.circuit.library import CXGate from qiskit.transpiler.passes import Layout2qDistance from qiskit.transpiler import CouplingMap, Layout from qiskit.converters import circuit_to_dag +from qiskit.transpiler.target import Target from qiskit.test import QiskitTestCase @@ -101,6 +103,54 @@ def test_swap_mapped_false(self): self.assertEqual(pass_.property_set["layout_score"], 1) + def test_swap_mapped_true_target(self): + """Mapped circuit. Good Layout + qr0 (0):--(+)---(+)- + | | + qr1 (1):---.-----|-- + | + qr2 (2):---------.-- + + CouplingMap map: [1]--[0]--[2] + """ + qr = QuantumRegister(3, "qr") + circuit = QuantumCircuit(qr) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[0], qr[2]) + target = Target() + target.add_instruction(CXGate(), {(0, 1): None, (0, 2): None}) + layout = Layout().generate_trivial_layout(qr) + + dag = circuit_to_dag(circuit) + pass_ = Layout2qDistance(target=target) + pass_.property_set["layout"] = layout + pass_.run(dag) + + self.assertEqual(pass_.property_set["layout_score"], 0) + + def test_swap_mapped_false_target(self): + """Needs [0]-[1] in a [0]--[2]--[1] Result:1 + qr0:--(+)-- + | + qr1:---.--- + + CouplingMap map: [0]--[2]--[1] + """ + qr = QuantumRegister(2, "qr") + circuit = QuantumCircuit(qr) + circuit.cx(qr[0], qr[1]) + target = Target() + target.add_instruction(CXGate(), {(0, 2): None, (2, 1): None}) + + layout = Layout().generate_trivial_layout(qr) + + dag = circuit_to_dag(circuit) + pass_ = Layout2qDistance(target=target) + pass_.property_set["layout"] = layout + pass_.run(dag) + + self.assertEqual(pass_.property_set["layout_score"], 1) + if __name__ == "__main__": unittest.main() diff --git a/test/python/transpiler/test_layout_transformation.py b/test/python/transpiler/test_layout_transformation.py index 5fe2fe315bc0..2832eee8192b 100644 --- a/test/python/transpiler/test_layout_transformation.py +++ b/test/python/transpiler/test_layout_transformation.py @@ -17,7 +17,8 @@ from qiskit import QuantumRegister, QuantumCircuit from qiskit.converters import circuit_to_dag from qiskit.test import QiskitTestCase -from qiskit.transpiler import CouplingMap, Layout +from qiskit.transpiler import CouplingMap, Layout, Target +from qiskit.circuit.library import CXGate from qiskit.transpiler.passes import LayoutTransformation @@ -65,6 +66,27 @@ def test_four_qubit(self): self.assertEqual(circuit_to_dag(expected), output_dag) + def test_four_qubit_with_target(self): + """Test if the permutation {0->3,1->0,2->1,3->2} is implemented correctly.""" + v = QuantumRegister(4, "v") # virtual qubits + target = Target() + target.add_instruction(CXGate(), {(0, 1): None, (1, 2): None, (2, 3): None}) + from_layout = Layout({v[0]: 0, v[1]: 1, v[2]: 2, v[3]: 3}) + to_layout = Layout({v[0]: 3, v[1]: 0, v[2]: 1, v[3]: 2}) + ltpass = LayoutTransformation( + target=target, from_layout=from_layout, to_layout=to_layout, seed=42 + ) + qc = QuantumCircuit(4) # input (empty) physical circuit + dag = circuit_to_dag(qc) + output_dag = ltpass.run(dag) + + expected = QuantumCircuit(4) + expected.swap(1, 0) + expected.swap(1, 2) + expected.swap(2, 3) + + self.assertEqual(circuit_to_dag(expected), output_dag) + def test_full_connected_coupling_map(self): """Test if the permutation {0->3,1->0,2->1,3->2} in a fully connected map.""" v = QuantumRegister(4, "v") # virtual qubits diff --git a/test/python/transpiler/test_lookahead_swap.py b/test/python/transpiler/test_lookahead_swap.py index c28a6f62d681..c510ccd4abd5 100644 --- a/test/python/transpiler/test_lookahead_swap.py +++ b/test/python/transpiler/test_lookahead_swap.py @@ -16,8 +16,9 @@ from numpy import pi from qiskit.dagcircuit import DAGCircuit from qiskit.transpiler.passes import LookaheadSwap -from qiskit.transpiler import CouplingMap +from qiskit.transpiler import CouplingMap, Target from qiskit.converters import circuit_to_dag +from qiskit.circuit.library import CXGate from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit from qiskit.test import QiskitTestCase from qiskit.providers.fake_provider import FakeMelbourne @@ -125,6 +126,35 @@ def test_lookahead_swap_maps_measurements(self): self.assertIn(mapped_measure_qargs, [{qr[0], qr[1]}, {qr[1], qr[2]}]) + def test_lookahead_swap_maps_measurements_with_target(self): + """Verify measurement nodes are updated to map correct cregs to re-mapped qregs. + + Create a circuit with measures on q0 and q2, following a swap between q0 and q2. + Since that swap is not in the coupling, one of the two will be required to move. + Verify that the mapped measure corresponds to one of the two possible layouts following + the swap. + + """ + + qr = QuantumRegister(3, "q") + cr = ClassicalRegister(2) + circuit = QuantumCircuit(qr, cr) + + circuit.cx(qr[0], qr[2]) + circuit.measure(qr[0], cr[0]) + circuit.measure(qr[2], cr[1]) + + dag_circuit = circuit_to_dag(circuit) + + target = Target() + target.add_instruction(CXGate(), {(0, 1): None, (1, 2): None}) + + mapped_dag = LookaheadSwap(target=target).run(dag_circuit) + + mapped_measure_qargs = {op.qargs[0] for op in mapped_dag.named_nodes("measure")} + + self.assertIn(mapped_measure_qargs, [{qr[0], qr[1]}, {qr[1], qr[2]}]) + def test_lookahead_swap_maps_barriers(self): """Verify barrier nodes are updated to re-mapped qregs. diff --git a/test/python/transpiler/test_trivial_layout.py b/test/python/transpiler/test_trivial_layout.py index f75478676b34..b71bffae6994 100644 --- a/test/python/transpiler/test_trivial_layout.py +++ b/test/python/transpiler/test_trivial_layout.py @@ -17,6 +17,8 @@ from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit from qiskit.transpiler import CouplingMap from qiskit.transpiler.passes import TrivialLayout +from qiskit.transpiler.target import Target +from qiskit.circuit.library import CXGate from qiskit.transpiler import TranspilerError from qiskit.converters import circuit_to_dag from qiskit.test import QiskitTestCase @@ -47,6 +49,24 @@ def test_3q_circuit_5q_coupling(self): for i in range(3): self.assertEqual(layout[qr[i]], i) + def test_3q_circuit_5q_coupling_with_target(self): + """Test finds trivial layout for 3q circuit on 5q device.""" + qr = QuantumRegister(3, "q") + circuit = QuantumCircuit(qr) + circuit.cx(qr[1], qr[0]) + circuit.cx(qr[0], qr[2]) + circuit.cx(qr[1], qr[2]) + + dag = circuit_to_dag(circuit) + target = Target() + target.add_instruction(CXGate(), {tuple(edge): None for edge in self.cmap5}) + pass_ = TrivialLayout(target=target) + pass_.run(dag) + layout = pass_.property_set["layout"] + + for i in range(3): + self.assertEqual(layout[qr[i]], i) + def test_9q_circuit_16q_coupling(self): """Test finds trivial layout for 9q circuit with 2 registers on 16q device.""" qr0 = QuantumRegister(4, "q0") From 86b00f076f18c8e8518ab3a13efbbd4dea781acb Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 19 Jan 2023 07:38:16 -0500 Subject: [PATCH 08/14] Fix lint --- qiskit/transpiler/passes/routing/layout_transformation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/routing/layout_transformation.py b/qiskit/transpiler/passes/routing/layout_transformation.py index 4c476770e068..1278199de54d 100644 --- a/qiskit/transpiler/passes/routing/layout_transformation.py +++ b/qiskit/transpiler/passes/routing/layout_transformation.py @@ -61,7 +61,7 @@ def __init__( precedence and the other argument will be ignored. Raises: - ValueError: If requried arguments ``from_layout`` or ``to_layout`` are not + TypeError: If requried arguments ``from_layout`` or ``to_layout`` are not specified. """ super().__init__() From 5c081c201ba2f72b7c1f916b92e6ad7b163f8389 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 20 Feb 2023 15:01:29 -0500 Subject: [PATCH 09/14] Fix reference dot for new preset pass managers --- .../references/pass_manager_standard.dot | 38 ++++++++++--------- .../references/pass_manager_style.dot | 38 ++++++++++--------- 2 files changed, 40 insertions(+), 36 deletions(-) diff --git a/test/python/visualization/references/pass_manager_standard.dot b/test/python/visualization/references/pass_manager_standard.dot index 38be3f5825ea..f58b5fd992e5 100644 --- a/test/python/visualization/references/pass_manager_standard.dot +++ b/test/python/visualization/references/pass_manager_standard.dot @@ -45,47 +45,49 @@ fontname=helvetica; label="[4] "; labeljust=l; 14 [color=blue, fontname=helvetica, label=Unroller, shape=rectangle]; -15 [color=black, fontname=helvetica, fontsize=10, label=basis, shape=ellipse, style=solid]; +15 [color=black, fontname=helvetica, fontsize=10, label=basis, shape=ellipse, style=dashed]; 15 -> 14; +16 [color=black, fontname=helvetica, fontsize=10, label=target, shape=ellipse, style=dashed]; +16 -> 14; 12 -> 14; } -subgraph cluster_16 { +subgraph cluster_17 { fontname=helvetica; label="[5] "; labeljust=l; -17 [color=red, fontname=helvetica, label=CheckMap, shape=rectangle]; -18 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=dashed]; -18 -> 17; -19 [color=black, fontname=helvetica, fontsize=10, label=target, shape=ellipse, style=dashed]; -19 -> 17; -14 -> 17; +18 [color=red, fontname=helvetica, label=CheckMap, shape=rectangle]; +19 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=dashed]; +19 -> 18; +20 [color=black, fontname=helvetica, fontsize=10, label=target, shape=ellipse, style=dashed]; +20 -> 18; +14 -> 18; } -subgraph cluster_20 { +subgraph cluster_21 { fontname=helvetica; label="[6] do_while"; labeljust=l; -21 [color=blue, fontname=helvetica, label=BarrierBeforeFinalMeasurements, shape=rectangle]; -17 -> 21; +22 [color=blue, fontname=helvetica, label=BarrierBeforeFinalMeasurements, shape=rectangle]; +18 -> 22; } subgraph cluster_23 { fontname=helvetica; label="[7] "; labeljust=l; -23 [color=blue, fontname=helvetica, label=CXDirection, shape=rectangle]; -24 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid]; -24 -> 23; -21 -> 23; +24 [color=blue, fontname=helvetica, label=CXDirection, shape=rectangle]; +25 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid]; +25 -> 24; +22 -> 24; } -subgraph cluster_25 { +subgraph cluster_26 { fontname=helvetica; label="[8] "; labeljust=l; -26 [color=blue, fontname=helvetica, label=RemoveResetInZeroState, shape=rectangle]; -23 -> 26; +27 [color=blue, fontname=helvetica, label=RemoveResetInZeroState, shape=rectangle]; +24 -> 27; } } diff --git a/test/python/visualization/references/pass_manager_style.dot b/test/python/visualization/references/pass_manager_style.dot index d822be0b1969..77086242cb6f 100644 --- a/test/python/visualization/references/pass_manager_style.dot +++ b/test/python/visualization/references/pass_manager_style.dot @@ -45,47 +45,49 @@ fontname=helvetica; label="[4] "; labeljust=l; 14 [color=blue, fontname=helvetica, label=Unroller, shape=rectangle]; -15 [color=black, fontname=helvetica, fontsize=10, label=basis, shape=ellipse, style=solid]; +15 [color=black, fontname=helvetica, fontsize=10, label=basis, shape=ellipse, style=dashed]; 15 -> 14; +16 [color=black, fontname=helvetica, fontsize=10, label=target, shape=ellipse, style=dashed]; +16 -> 14; 12 -> 14; } -subgraph cluster_16 { +subgraph cluster_17 { fontname=helvetica; label="[5] "; labeljust=l; -17 [color=green, fontname=helvetica, label=CheckMap, shape=rectangle]; -18 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=dashed]; -18 -> 17; -19 [color=black, fontname=helvetica, fontsize=10, label=target, shape=ellipse, style=dashed]; -19 -> 17; -14 -> 17; +18 [color=green, fontname=helvetica, label=CheckMap, shape=rectangle]; +19 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=dashed]; +19 -> 18; +20 [color=black, fontname=helvetica, fontsize=10, label=target, shape=ellipse, style=dashed]; +20 -> 18; +14 -> 18; } -subgraph cluster_20 { +subgraph cluster_21 { fontname=helvetica; label="[6] do_while"; labeljust=l; -21 [color=blue, fontname=helvetica, label=BarrierBeforeFinalMeasurements, shape=rectangle]; -17 -> 21; +22 [color=blue, fontname=helvetica, label=BarrierBeforeFinalMeasurements, shape=rectangle]; +18 -> 22; } subgraph cluster_23 { fontname=helvetica; label="[7] "; labeljust=l; -23 [color=blue, fontname=helvetica, label=CXDirection, shape=rectangle]; -24 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid]; -24 -> 23; -21 -> 23; +24 [color=blue, fontname=helvetica, label=CXDirection, shape=rectangle]; +25 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid]; +25 -> 24; +22 -> 24; } -subgraph cluster_25 { +subgraph cluster_26 { fontname=helvetica; label="[8] "; labeljust=l; -26 [color=grey, fontname=helvetica, label=RemoveResetInZeroState, shape=rectangle]; -23 -> 26; +27 [color=grey, fontname=helvetica, label=RemoveResetInZeroState, shape=rectangle]; +24 -> 27; } } From 737b6d86f817567a57a7a7ee71165d115ea5443e Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 3 Apr 2023 11:26:20 -0400 Subject: [PATCH 10/14] 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. --- qiskit/transpiler/passes/layout/csp_layout.py | 20 +++--- .../passes/layout/full_ancilla_allocation.py | 18 ++--- .../passes/layout/layout_2q_distance.py | 19 +++-- .../passes/layout/noise_adaptive_layout.py | 16 +++-- .../transpiler/passes/layout/sabre_layout.py | 17 +++-- .../passes/layout/trivial_layout.py | 16 +++-- .../transpiler/passes/routing/basic_swap.py | 18 ++--- .../transpiler/passes/routing/bip_mapping.py | 27 ++++--- .../passes/routing/layout_transformation.py | 31 +++----- .../passes/routing/lookahead_swap.py | 17 ++--- .../transpiler/passes/routing/sabre_swap.py | 20 +++--- .../passes/routing/stochastic_swap.py | 20 +++--- .../preset_passmanagers/builtin_plugins.py | 58 +++++++++------ .../transpiler/preset_passmanagers/common.py | 9 ++- .../transpiler/preset_passmanagers/level0.py | 15 ++-- .../transpiler/preset_passmanagers/level1.py | 20 ++++-- .../transpiler/preset_passmanagers/level2.py | 15 ++-- .../transpiler/preset_passmanagers/level3.py | 15 ++-- test/python/transpiler/test_basic_swap.py | 2 +- test/python/transpiler/test_csp_layout.py | 2 +- test/python/transpiler/test_layout_score.py | 4 +- .../transpiler/test_layout_transformation.py | 4 +- test/python/transpiler/test_lookahead_swap.py | 2 +- test/python/transpiler/test_trivial_layout.py | 2 +- .../references/pass_manager_standard.dot | 72 +++++++++---------- .../references/pass_manager_style.dot | 72 +++++++++---------- 26 files changed, 276 insertions(+), 255 deletions(-) diff --git a/qiskit/transpiler/passes/layout/csp_layout.py b/qiskit/transpiler/passes/layout/csp_layout.py index 357c3f019623..afcba241c3ec 100644 --- a/qiskit/transpiler/passes/layout/csp_layout.py +++ b/qiskit/transpiler/passes/layout/csp_layout.py @@ -20,6 +20,7 @@ from qiskit.transpiler.layout import Layout from qiskit.transpiler.basepasses import AnalysisPass from qiskit.utils import optionals as _optionals +from qiskit.transpiler.target import Target @_optionals.HAS_CONSTRAINT.require_in_instance @@ -28,12 +29,11 @@ class CSPLayout(AnalysisPass): def __init__( self, - coupling_map=None, + coupling_map, strict_direction=False, seed=None, call_limit=1000, time_limit=10, - target=None, ): """If possible, chooses a Layout as a CSP, using backtracking. @@ -47,7 +47,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. @@ -56,19 +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 + 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 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""" diff --git a/qiskit/transpiler/passes/layout/full_ancilla_allocation.py b/qiskit/transpiler/passes/layout/full_ancilla_allocation.py index c13dd4d82c5c..e3259b4d0ad9 100644 --- a/qiskit/transpiler/passes/layout/full_ancilla_allocation.py +++ b/qiskit/transpiler/passes/layout/full_ancilla_allocation.py @@ -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): @@ -31,21 +32,20 @@ class FullAncillaAllocation(AnalysisPass): circuit. """ - def __init__(self, coupling_map=None, target=None): + def __init__(self, coupling_map): """FullAncillaAllocation 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. + coupling_map (Union[CouplingMap, Target]): directed graph representing a coupling map. """ super().__init__() - self.coupling_map = coupling_map - self.ancilla_name = "ancilla" - self.target = target - if self.target is not None: + 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): """Run the FullAncillaAllocation pass on `dag`. diff --git a/qiskit/transpiler/passes/layout/layout_2q_distance.py b/qiskit/transpiler/passes/layout/layout_2q_distance.py index 39534512585b..6af709851702 100644 --- a/qiskit/transpiler/passes/layout/layout_2q_distance.py +++ b/qiskit/transpiler/passes/layout/layout_2q_distance.py @@ -19,6 +19,7 @@ """ from qiskit.transpiler.basepasses import AnalysisPass +from qiskit.transpiler.target import Target class Layout2qDistance(AnalysisPass): @@ -30,23 +31,21 @@ class Layout2qDistance(AnalysisPass): No CX direction is considered. """ - def __init__(self, coupling_map=None, property_name="layout_score", target=None): + 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 - 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: + 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): """ diff --git a/qiskit/transpiler/passes/layout/noise_adaptive_layout.py b/qiskit/transpiler/passes/layout/noise_adaptive_layout.py index 773cc98992b1..cfe08eba4dd2 100644 --- a/qiskit/transpiler/passes/layout/noise_adaptive_layout.py +++ b/qiskit/transpiler/passes/layout/noise_adaptive_layout.py @@ -19,7 +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 +from qiskit.transpiler.target import target_to_backend_properties, Target class NoiseAdaptiveLayout(AnalysisPass): @@ -55,11 +55,11 @@ class NoiseAdaptiveLayout(AnalysisPass): by being set in `property_set`. """ - def __init__(self, backend_prop=None, target=None): + def __init__(self, backend_prop): """NoiseAdaptiveLayout initializer. Args: - backend_prop (BackendProperties): backend properties object + backend_prop (Union[BackendProperties, Target]): 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. @@ -68,7 +68,12 @@ def __init__(self, backend_prop=None, target=None): 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 = {} @@ -81,9 +86,6 @@ def __init__(self, backend_prop=None, target=None): 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.""" diff --git a/qiskit/transpiler/passes/layout/sabre_layout.py b/qiskit/transpiler/passes/layout/sabre_layout.py index 97d1daf92b14..d85780d7ecf0 100644 --- a/qiskit/transpiler/passes/layout/sabre_layout.py +++ b/qiskit/transpiler/passes/layout/sabre_layout.py @@ -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__) @@ -80,14 +81,13 @@ def __init__( seed=None, max_iterations=3, swap_trials=None, - target=None, layout_trials=None, skip_routing=False, ): """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 @@ -107,9 +107,6 @@ 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. layout_trials (int): The number of random seed trials to run layout with. When > 1 the trial that resuls in the output with the fewest swap gates will be selected. If this is not specified @@ -128,7 +125,12 @@ 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 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") @@ -136,9 +138,6 @@ def __init__( self.seed = seed self.max_iterations = max_iterations self.trials = swap_trials - self.target = target - if self.target is not None: - self.coupling_map = self.target.build_coupling_map() if swap_trials is None: self.swap_trials = CPU_COUNT else: diff --git a/qiskit/transpiler/passes/layout/trivial_layout.py b/qiskit/transpiler/passes/layout/trivial_layout.py index 865d0d2e8019..bdbb9896e576 100644 --- a/qiskit/transpiler/passes/layout/trivial_layout.py +++ b/qiskit/transpiler/passes/layout/trivial_layout.py @@ -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): @@ -29,21 +30,22 @@ class TrivialLayout(AnalysisPass): Does not assume any ancilla. """ - def __init__(self, coupling_map=None, target=None): + def __init__(self, coupling_map): """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. + coupling_map (Union[CouplingMap, Target]): directed graph representing a coupling map. Raises: TranspilerError: if invalid options """ super().__init__() - self.coupling_map = coupling_map - self.target = target + 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`. diff --git a/qiskit/transpiler/passes/routing/basic_swap.py b/qiskit/transpiler/passes/routing/basic_swap.py index 859c24fb0476..91a4d4ded611 100644 --- a/qiskit/transpiler/passes/routing/basic_swap.py +++ b/qiskit/transpiler/passes/routing/basic_swap.py @@ -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): @@ -27,23 +28,22 @@ class BasicSwap(TransformationPass): one or more swaps in front to make it compatible. """ - def __init__(self, coupling_map=None, fake_run=False, target=None): + 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. - 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: + 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): """Run the BasicSwap pass on `dag`. diff --git a/qiskit/transpiler/passes/routing/bip_mapping.py b/qiskit/transpiler/passes/routing/bip_mapping.py index c4e4afda8a24..9f6fc177eece 100644 --- a/qiskit/transpiler/passes/routing/bip_mapping.py +++ b/qiskit/transpiler/passes/routing/bip_mapping.py @@ -22,7 +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 +from qiskit.transpiler.target import target_to_backend_properties, Target logger = logging.getLogger(__name__) @@ -65,7 +65,7 @@ class BIPMapping(TransformationPass): def __init__( self, - coupling_map=None, + coupling_map, qubit_subset=None, objective="balanced", backend_prop=None, @@ -74,12 +74,11 @@ def __init__( max_swaps_inbetween_layers=None, depth_obj_weight=0.1, default_cx_error_rate=5e-3, - target=None, ): """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: @@ -108,29 +107,29 @@ 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. 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 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 - 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())) diff --git a/qiskit/transpiler/passes/routing/layout_transformation.py b/qiskit/transpiler/passes/routing/layout_transformation.py index 1278199de54d..3d67e0ffb572 100644 --- a/qiskit/transpiler/passes/routing/layout_transformation.py +++ b/qiskit/transpiler/passes/routing/layout_transformation.py @@ -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): @@ -30,17 +31,16 @@ class LayoutTransformation(TransformationPass): def __init__( self, - coupling_map: CouplingMap = None, - from_layout: Union[Layout, str] = None, - to_layout: Union[Layout, str] = None, + coupling_map: Union[CouplingMap, Target, None], + from_layout: Union[Layout, str], + to_layout: Union[Layout, str], seed: Union[int, np.random.default_rng] = None, trials=4, - target=None, ): """LayoutTransformation initializer. Args: - coupling_map (CouplingMap): + coupling_map: Directed graph representing a coupling map. from_layout (Union[Layout, str]): @@ -56,25 +56,16 @@ 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. - - Raises: - TypeError: If requried arguments ``from_layout`` or ``to_layout`` are not - specified. """ super().__init__() - if from_layout is None: - raise TypeError("Required from_layout argument not provided.") - if to_layout is None: - raise TypeError("Required to_layout argument not provided.") self.from_layout = from_layout self.to_layout = to_layout - self.coupling_map = coupling_map - self.target = target - if self.target is not None: - self.coupling_map = target.build_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 if self.coupling_map is None: self.coupling_map = CouplingMap.from_full(len(to_layout)) graph = self.coupling_map.graph.to_undirected() diff --git a/qiskit/transpiler/passes/routing/lookahead_swap.py b/qiskit/transpiler/passes/routing/lookahead_swap.py index 7659c183a1da..e48e2b59e4ad 100644 --- a/qiskit/transpiler/passes/routing/lookahead_swap.py +++ b/qiskit/transpiler/passes/routing/lookahead_swap.py @@ -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__) @@ -81,13 +82,11 @@ class LookaheadSwap(TransformationPass): https://medium.com/qiskit/improving-a-quantum-compiler-48410d7a7084 """ - def __init__( - self, coupling_map=None, search_depth=4, search_width=4, fake_run=False, target=None - ): + 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 @@ -98,13 +97,15 @@ def __init__( """ 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 - 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`. diff --git a/qiskit/transpiler/passes/routing/sabre_swap.py b/qiskit/transpiler/passes/routing/sabre_swap.py index 58dabb777280..b0690417c339 100644 --- a/qiskit/transpiler/passes/routing/sabre_swap.py +++ b/qiskit/transpiler/passes/routing/sabre_swap.py @@ -21,6 +21,7 @@ from qiskit.transpiler.basepasses import TransformationPass from qiskit.transpiler.exceptions import TranspilerError from qiskit.transpiler.layout import Layout +from qiskit.transpiler.target import Target from qiskit.dagcircuit import DAGOpNode from qiskit.tools.parallel import CPU_COUNT @@ -72,13 +73,11 @@ class SabreSwap(TransformationPass): `arXiv:1809.02573 `_ """ - def __init__( - self, coupling_map, heuristic="basic", seed=None, fake_run=False, trials=None, target=None - ): + def __init__(self, coupling_map, heuristic="basic", seed=None, fake_run=False, trials=None): r"""SabreSwap initializer. Args: - coupling_map (CouplingMap): CouplingMap of the target backend. + coupling_map (Union[CouplingMap, Target]): CouplingMap of the target backend. heuristic (str): The type of heuristic to use when deciding best swap strategy ('basic' or 'lookahead' or 'decay'). seed (int): random seed used to tie-break among candidate swaps. @@ -90,9 +89,6 @@ def __init__( 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. @@ -144,10 +140,12 @@ def __init__( super().__init__() # Assume bidirectional couplings, fixing gate direction is easy later. - self.coupling_map = coupling_map - self.target = target - if self.target is not None: + if isinstance(coupling_map, Target): + self.target = coupling_map self.coupling_map = self.target.build_coupling_map() + else: + self.coupling_map = coupling_map + self.target = None if self.coupling_map is not None and not self.coupling_map.is_symmetric: # A deepcopy is needed here to avoid modifications updating # shared references in passes which require directional @@ -155,7 +153,7 @@ def __init__( self.coupling_map = deepcopy(coupling_map) self.coupling_map.make_symmetric() self._neighbor_table = None - if coupling_map is not None: + if self.coupling_map is not None: self._neighbor_table = NeighborTable( rustworkx.adjacency_matrix(self.coupling_map.graph) ) diff --git a/qiskit/transpiler/passes/routing/stochastic_swap.py b/qiskit/transpiler/passes/routing/stochastic_swap.py index 1f7e41302c62..df549ef848bc 100644 --- a/qiskit/transpiler/passes/routing/stochastic_swap.py +++ b/qiskit/transpiler/passes/routing/stochastic_swap.py @@ -24,6 +24,7 @@ from qiskit.dagcircuit import DAGCircuit from qiskit.circuit.library.standard_gates import SwapGate from qiskit.transpiler.layout import Layout +from qiskit.transpiler.target import Target from qiskit.circuit import IfElseOp, WhileLoopOp, ForLoopOp, ControlFlowOp, Instruction from qiskit._accelerate import stochastic_swap as stochastic_swap_rs from qiskit._accelerate import nlayout @@ -48,9 +49,7 @@ class StochasticSwap(TransformationPass): the circuit. """ - def __init__( - self, coupling_map, trials=20, seed=None, fake_run=False, initial_layout=None, target=None - ): + def __init__(self, coupling_map, trials=20, seed=None, fake_run=False, initial_layout=None): """StochasticSwap initializer. The coupling map is a connected graph @@ -58,19 +57,21 @@ def __init__( If these are not satisfied, the behavior is undefined. Args: - coupling_map (CouplingMap): Directed graph representing a coupling + coupling_map (Union[CouplingMap, Target]): Directed graph representing a coupling map. trials (int): maximum number of iterations to attempt seed (int): seed for random number generator fake_run (bool): if true, it only pretend to do routing, i.e., no swap is effectively added. initial_layout (Layout): starting layout at beginning of pass. - 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 + 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.trials = trials self.seed = seed self.rng = None @@ -79,9 +80,6 @@ def __init__( self.initial_layout = initial_layout self._qubit_to_int = None self._int_to_qubit = None - self.target = target - if self.target is not None: - self.coupling_map = self.target.build_coupling_map() def run(self, dag): """Run the StochasticSwap pass on `dag`. diff --git a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py index 83ccd86e9a75..79f28d601802 100644 --- a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py +++ b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py @@ -32,7 +32,10 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana target = pass_manager_config.target coupling_map = pass_manager_config.coupling_map backend_properties = pass_manager_config.backend_properties - routing_pass = BasicSwap(coupling_map, target=target) + if coupling_map is None: + routing_pass = BasicSwap(target) + else: + routing_pass = BasicSwap(coupling_map) vf2_call_limit = common.get_vf2_call_limit( optimization_level, pass_manager_config.layout_method, @@ -88,6 +91,9 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana seed_transpiler = pass_manager_config.seed_transpiler target = pass_manager_config.target coupling_map = pass_manager_config.coupling_map + coupling_map_routing = coupling_map + if coupling_map_routing is None: + coupling_map_routing = target backend_properties = pass_manager_config.backend_properties vf2_call_limit = common.get_vf2_call_limit( optimization_level, @@ -95,13 +101,9 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana pass_manager_config.initial_layout, ) if optimization_level == 3: - routing_pass = StochasticSwap( - coupling_map, trials=200, seed=seed_transpiler, target=target - ) + routing_pass = StochasticSwap(coupling_map_routing, trials=200, seed=seed_transpiler) else: - routing_pass = StochasticSwap( - coupling_map, trials=20, seed=seed_transpiler, target=target - ) + routing_pass = StochasticSwap(coupling_map_routing, trials=20, seed=seed_transpiler) if optimization_level == 0: return common.generate_routing_passmanager( @@ -143,6 +145,9 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana seed_transpiler = pass_manager_config.seed_transpiler target = pass_manager_config.target coupling_map = pass_manager_config.coupling_map + coupling_map_routing = coupling_map + if coupling_map_routing is None: + coupling_map_routing = target backend_properties = pass_manager_config.backend_properties vf2_call_limit = common.get_vf2_call_limit( optimization_level, @@ -150,9 +155,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana pass_manager_config.initial_layout, ) if optimization_level == 0: - routing_pass = LookaheadSwap( - coupling_map, search_depth=2, search_width=2, target=target - ) + routing_pass = LookaheadSwap(coupling_map_routing, search_depth=2, search_width=2) return common.generate_routing_passmanager( routing_pass, target, @@ -161,9 +164,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana use_barrier_before_measurement=True, ) if optimization_level == 1: - routing_pass = LookaheadSwap( - coupling_map, search_depth=4, search_width=4, target=target - ) + routing_pass = LookaheadSwap(coupling_map_routing, search_depth=4, search_width=4) return common.generate_routing_passmanager( routing_pass, target, @@ -175,9 +176,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana use_barrier_before_measurement=True, ) if optimization_level == 2: - routing_pass = LookaheadSwap( - coupling_map, search_depth=5, search_width=6, target=target - ) + routing_pass = LookaheadSwap(coupling_map_routing, search_depth=5, search_width=6) return common.generate_routing_passmanager( routing_pass, target, @@ -188,9 +187,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana use_barrier_before_measurement=True, ) if optimization_level == 3: - routing_pass = LookaheadSwap( - coupling_map, search_depth=5, search_width=6, target=target - ) + routing_pass = LookaheadSwap(coupling_map_routing, search_depth=5, search_width=6) return common.generate_routing_passmanager( routing_pass, target, @@ -211,6 +208,9 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana seed_transpiler = pass_manager_config.seed_transpiler target = pass_manager_config.target coupling_map = pass_manager_config.coupling_map + coupling_map_routing = coupling_map + if coupling_map_routing is None: + coupling_map_routing = target backend_properties = pass_manager_config.backend_properties vf2_call_limit = common.get_vf2_call_limit( optimization_level, @@ -219,7 +219,10 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana ) if optimization_level == 0: routing_pass = SabreSwap( - coupling_map, heuristic="basic", seed=seed_transpiler, trials=5, target=target + coupling_map_routing, + heuristic="basic", + seed=seed_transpiler, + trials=5, ) return common.generate_routing_passmanager( routing_pass, @@ -230,7 +233,10 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana ) if optimization_level == 1: routing_pass = SabreSwap( - coupling_map, heuristic="decay", seed=seed_transpiler, trials=5, target=target + coupling_map_routing, + heuristic="decay", + seed=seed_transpiler, + trials=5, ) return common.generate_routing_passmanager( routing_pass, @@ -244,7 +250,10 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana ) if optimization_level == 2: routing_pass = SabreSwap( - coupling_map, heuristic="decay", seed=seed_transpiler, trials=10, target=target + coupling_map_routing, + heuristic="decay", + seed=seed_transpiler, + trials=10, ) return common.generate_routing_passmanager( routing_pass, @@ -257,7 +266,10 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana ) if optimization_level == 3: routing_pass = SabreSwap( - coupling_map, heuristic="decay", seed=seed_transpiler, trials=20, target=target + coupling_map_routing, + heuristic="decay", + seed=seed_transpiler, + trials=20, ) return common.generate_routing_passmanager( routing_pass, diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index 153693ce98a2..ae62d0f4a3fb 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -211,9 +211,12 @@ def generate_embed_passmanager(coupling_map=None, target=None): PassManager: The embedding passmanager that assumes the layout property set has been set in earlier stages """ - return PassManager( - [FullAncillaAllocation(coupling_map, target=target), EnlargeWithAncilla(), ApplyLayout()] - ) + if coupling_map is None: + cmap = target + else: + cmap = coupling_map + + return PassManager([FullAncillaAllocation(cmap), EnlargeWithAncilla(), ApplyLayout()]) def _layout_not_perfect(property_set): diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index a3e23b557d37..920e314825c1 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -77,22 +77,29 @@ def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa def _choose_layout_condition(property_set): return not property_set["layout"] + if coupling_map is None: + coupling_map_layout = target + else: + coupling_map_layout = coupling_map + if layout_method == "trivial": - _choose_layout = TrivialLayout(coupling_map, target=target) + _choose_layout = TrivialLayout(coupling_map_layout) elif layout_method == "dense": _choose_layout = DenseLayout(coupling_map, backend_properties, target=target) elif layout_method == "noise_adaptive": - _choose_layout = NoiseAdaptiveLayout(backend_properties, target=target) + if backend_properties is None: + _choose_layout = NoiseAdaptiveLayout(target) + else: + _choose_layout = NoiseAdaptiveLayout(backend_properties) elif layout_method == "sabre": skip_routing = pass_manager_config.routing_method is not None and routing_method != "sabre" _choose_layout = SabreLayout( - coupling_map, + coupling_map_layout, max_iterations=1, seed=seed_transpiler, swap_trials=5, layout_trials=5, skip_routing=skip_routing, - target=target, ) # Choose routing pass diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index e2e2fd707768..43ed21bb3854 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -113,10 +113,15 @@ def _vf2_match_not_found(property_set): return True return False + if coupling_map is None: + coupling_map_layout = target + else: + coupling_map_layout = coupling_map + _choose_layout_0 = ( [] if pass_manager_config.layout_method - else [TrivialLayout(coupling_map, target=target), CheckMap(coupling_map, target=target)] + else [TrivialLayout(coupling_map_layout), CheckMap(coupling_map, target=target)] ) _choose_layout_1 = ( @@ -132,34 +137,35 @@ def _vf2_match_not_found(property_set): ) if layout_method == "trivial": - _improve_layout = TrivialLayout(coupling_map, target=target) + _improve_layout = TrivialLayout(coupling_map_layout) elif layout_method == "dense": _improve_layout = DenseLayout(coupling_map, backend_properties, target=target) elif layout_method == "noise_adaptive": - _improve_layout = NoiseAdaptiveLayout(backend_properties, target=target) + if backend_properties is None: + _improve_layout = NoiseAdaptiveLayout(target) + else: + _improve_layout = NoiseAdaptiveLayout(backend_properties) elif layout_method == "sabre": _improve_layout = SabreLayout( - coupling_map, + coupling_map_layout, max_iterations=2, seed=seed_transpiler, swap_trials=5, layout_trials=5, skip_routing=pass_manager_config.routing_method is not None and routing_method != "sabre", - target=target, ) elif layout_method is None: _improve_layout = common.if_has_control_flow_else( DenseLayout(coupling_map, backend_properties, target=target), SabreLayout( - coupling_map, + coupling_map_layout, max_iterations=2, seed=seed_transpiler, swap_trials=5, layout_trials=5, skip_routing=pass_manager_config.routing_method is not None and routing_method != "sabre", - target=target, ), ).to_flow_controller() diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index d456c563298f..2e9dfe277269 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -121,22 +121,29 @@ def _vf2_match_not_found(property_set): ) ) + if coupling_map is None: + coupling_map_layout = target + else: + coupling_map_layout = coupling_map + if layout_method == "trivial": - _choose_layout_1 = TrivialLayout(coupling_map, target=target) + _choose_layout_1 = TrivialLayout(coupling_map_layout) elif layout_method == "dense": _choose_layout_1 = DenseLayout(coupling_map, backend_properties, target=target) elif layout_method == "noise_adaptive": - _choose_layout_1 = NoiseAdaptiveLayout(backend_properties, target=target) + if backend_properties is None: + _choose_layout_1 = NoiseAdaptiveLayout(target) + else: + _choose_layout_1 = NoiseAdaptiveLayout(backend_properties) elif layout_method == "sabre": _choose_layout_1 = SabreLayout( - coupling_map, + coupling_map_layout, max_iterations=2, seed=seed_transpiler, swap_trials=10, layout_trials=10, skip_routing=pass_manager_config.routing_method is not None and routing_method != "sabre", - target=target, ) # Choose routing pass diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index 1640706dcf96..b8c3e311eba5 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -126,23 +126,30 @@ def _vf2_match_not_found(property_set): target=target, ) ) + + if coupling_map is None: + coupling_map_layout = target + else: + coupling_map_layout = coupling_map # 2b. if VF2 didn't converge on a solution use layout_method (dense). if layout_method == "trivial": - _choose_layout_1 = TrivialLayout(coupling_map, target=target) + _choose_layout_1 = TrivialLayout(coupling_map_layout) elif layout_method == "dense": _choose_layout_1 = DenseLayout(coupling_map, backend_properties, target=target) elif layout_method == "noise_adaptive": - _choose_layout_1 = NoiseAdaptiveLayout(backend_properties, target=target) + if backend_properties is None: + _choose_layout_1 = NoiseAdaptiveLayout(target) + else: + _choose_layout_1 = NoiseAdaptiveLayout(backend_properties) elif layout_method == "sabre": _choose_layout_1 = SabreLayout( - coupling_map, + coupling_map_layout, max_iterations=4, seed=seed_transpiler, swap_trials=20, layout_trials=20, skip_routing=pass_manager_config.routing_method is not None and routing_method != "sabre", - target=target, ) # Choose routing pass diff --git a/test/python/transpiler/test_basic_swap.py b/test/python/transpiler/test_basic_swap.py index 777850408821..f9b9f7824eed 100644 --- a/test/python/transpiler/test_basic_swap.py +++ b/test/python/transpiler/test_basic_swap.py @@ -135,7 +135,7 @@ def test_a_single_swap_with_target(self): expected.swap(qr[1], qr[0]) expected.cx(qr[0], qr[2]) - pass_ = BasicSwap(target=target) + pass_ = BasicSwap(target) after = pass_.run(dag) self.assertEqual(circuit_to_dag(expected), after) diff --git a/test/python/transpiler/test_csp_layout.py b/test/python/transpiler/test_csp_layout.py index ba04566a8119..14c0ed13791f 100644 --- a/test/python/transpiler/test_csp_layout.py +++ b/test/python/transpiler/test_csp_layout.py @@ -89,7 +89,7 @@ def test_3q_circuit_5q_coupling_with_target(self): circuit.cx(qr[1], qr[2]) # qr1 -> qr2 dag = circuit_to_dag(circuit) - pass_ = CSPLayout(target=target, strict_direction=False, seed=self.seed) + pass_ = CSPLayout(target, strict_direction=False, seed=self.seed) pass_.run(dag) layout = pass_.property_set["layout"] diff --git a/test/python/transpiler/test_layout_score.py b/test/python/transpiler/test_layout_score.py index ed690fb4d863..7d60b24d0062 100644 --- a/test/python/transpiler/test_layout_score.py +++ b/test/python/transpiler/test_layout_score.py @@ -122,7 +122,7 @@ def test_swap_mapped_true_target(self): layout = Layout().generate_trivial_layout(qr) dag = circuit_to_dag(circuit) - pass_ = Layout2qDistance(target=target) + pass_ = Layout2qDistance(target) pass_.property_set["layout"] = layout pass_.run(dag) @@ -145,7 +145,7 @@ def test_swap_mapped_false_target(self): layout = Layout().generate_trivial_layout(qr) dag = circuit_to_dag(circuit) - pass_ = Layout2qDistance(target=target) + pass_ = Layout2qDistance(target) pass_.property_set["layout"] = layout pass_.run(dag) diff --git a/test/python/transpiler/test_layout_transformation.py b/test/python/transpiler/test_layout_transformation.py index 2832eee8192b..6d38f744ced1 100644 --- a/test/python/transpiler/test_layout_transformation.py +++ b/test/python/transpiler/test_layout_transformation.py @@ -73,9 +73,7 @@ def test_four_qubit_with_target(self): target.add_instruction(CXGate(), {(0, 1): None, (1, 2): None, (2, 3): None}) from_layout = Layout({v[0]: 0, v[1]: 1, v[2]: 2, v[3]: 3}) to_layout = Layout({v[0]: 3, v[1]: 0, v[2]: 1, v[3]: 2}) - ltpass = LayoutTransformation( - target=target, from_layout=from_layout, to_layout=to_layout, seed=42 - ) + ltpass = LayoutTransformation(target, from_layout=from_layout, to_layout=to_layout, seed=42) qc = QuantumCircuit(4) # input (empty) physical circuit dag = circuit_to_dag(qc) output_dag = ltpass.run(dag) diff --git a/test/python/transpiler/test_lookahead_swap.py b/test/python/transpiler/test_lookahead_swap.py index c510ccd4abd5..70e8ae111daa 100644 --- a/test/python/transpiler/test_lookahead_swap.py +++ b/test/python/transpiler/test_lookahead_swap.py @@ -149,7 +149,7 @@ def test_lookahead_swap_maps_measurements_with_target(self): target = Target() target.add_instruction(CXGate(), {(0, 1): None, (1, 2): None}) - mapped_dag = LookaheadSwap(target=target).run(dag_circuit) + mapped_dag = LookaheadSwap(target).run(dag_circuit) mapped_measure_qargs = {op.qargs[0] for op in mapped_dag.named_nodes("measure")} diff --git a/test/python/transpiler/test_trivial_layout.py b/test/python/transpiler/test_trivial_layout.py index b71bffae6994..89c3acf1db48 100644 --- a/test/python/transpiler/test_trivial_layout.py +++ b/test/python/transpiler/test_trivial_layout.py @@ -60,7 +60,7 @@ def test_3q_circuit_5q_coupling_with_target(self): dag = circuit_to_dag(circuit) target = Target() target.add_instruction(CXGate(), {tuple(edge): None for edge in self.cmap5}) - pass_ = TrivialLayout(target=target) + pass_ = TrivialLayout(target) pass_.run(dag) layout = pass_.property_set["layout"] diff --git a/test/python/visualization/references/pass_manager_standard.dot b/test/python/visualization/references/pass_manager_standard.dot index f58b5fd992e5..ba79a7e900c4 100644 --- a/test/python/visualization/references/pass_manager_standard.dot +++ b/test/python/visualization/references/pass_manager_standard.dot @@ -13,81 +13,77 @@ fontname=helvetica; label="[1] condition"; labeljust=l; 4 [color=red, fontname=helvetica, label=TrivialLayout, shape=rectangle]; -5 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=dashed]; +5 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid]; 5 -> 4; -6 [color=black, fontname=helvetica, fontsize=10, label=target, shape=ellipse, style=dashed]; -6 -> 4; 1 -> 4; } -subgraph cluster_7 { +subgraph cluster_6 { fontname=helvetica; label="[2] "; labeljust=l; -8 [color=red, fontname=helvetica, label=FullAncillaAllocation, shape=rectangle]; -9 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=dashed]; -9 -> 8; -10 [color=black, fontname=helvetica, fontsize=10, label=target, shape=ellipse, style=dashed]; -10 -> 8; -4 -> 8; +7 [color=red, fontname=helvetica, label=FullAncillaAllocation, shape=rectangle]; +8 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid]; +8 -> 7; +4 -> 7; } -subgraph cluster_11 { +subgraph cluster_9 { fontname=helvetica; label="[3] "; labeljust=l; -12 [color=blue, fontname=helvetica, label=EnlargeWithAncilla, shape=rectangle]; -8 -> 12; +10 [color=blue, fontname=helvetica, label=EnlargeWithAncilla, shape=rectangle]; +7 -> 10; } -subgraph cluster_13 { +subgraph cluster_11 { fontname=helvetica; label="[4] "; labeljust=l; -14 [color=blue, fontname=helvetica, label=Unroller, shape=rectangle]; -15 [color=black, fontname=helvetica, fontsize=10, label=basis, shape=ellipse, style=dashed]; -15 -> 14; -16 [color=black, fontname=helvetica, fontsize=10, label=target, shape=ellipse, style=dashed]; -16 -> 14; -12 -> 14; +12 [color=blue, fontname=helvetica, label=Unroller, shape=rectangle]; +13 [color=black, fontname=helvetica, fontsize=10, label=basis, shape=ellipse, style=dashed]; +13 -> 12; +14 [color=black, fontname=helvetica, fontsize=10, label=target, shape=ellipse, style=dashed]; +14 -> 12; +10 -> 12; } -subgraph cluster_17 { +subgraph cluster_15 { fontname=helvetica; label="[5] "; labeljust=l; -18 [color=red, fontname=helvetica, label=CheckMap, shape=rectangle]; -19 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=dashed]; -19 -> 18; -20 [color=black, fontname=helvetica, fontsize=10, label=target, shape=ellipse, style=dashed]; -20 -> 18; -14 -> 18; +16 [color=red, fontname=helvetica, label=CheckMap, shape=rectangle]; +17 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=dashed]; +17 -> 16; +18 [color=black, fontname=helvetica, fontsize=10, label=target, shape=ellipse, style=dashed]; +18 -> 16; +12 -> 16; } -subgraph cluster_21 { +subgraph cluster_19 { fontname=helvetica; label="[6] do_while"; labeljust=l; -22 [color=blue, fontname=helvetica, label=BarrierBeforeFinalMeasurements, shape=rectangle]; -18 -> 22; +20 [color=blue, fontname=helvetica, label=BarrierBeforeFinalMeasurements, shape=rectangle]; +16 -> 20; } -subgraph cluster_23 { +subgraph cluster_21 { fontname=helvetica; label="[7] "; labeljust=l; -24 [color=blue, fontname=helvetica, label=CXDirection, shape=rectangle]; -25 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid]; -25 -> 24; -22 -> 24; +22 [color=blue, fontname=helvetica, label=CXDirection, shape=rectangle]; +23 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid]; +23 -> 22; +20 -> 22; } -subgraph cluster_26 { +subgraph cluster_24 { fontname=helvetica; label="[8] "; labeljust=l; -27 [color=blue, fontname=helvetica, label=RemoveResetInZeroState, shape=rectangle]; -24 -> 27; +25 [color=blue, fontname=helvetica, label=RemoveResetInZeroState, shape=rectangle]; +22 -> 25; } } diff --git a/test/python/visualization/references/pass_manager_style.dot b/test/python/visualization/references/pass_manager_style.dot index 77086242cb6f..9f77e82c63eb 100644 --- a/test/python/visualization/references/pass_manager_style.dot +++ b/test/python/visualization/references/pass_manager_style.dot @@ -13,81 +13,77 @@ fontname=helvetica; label="[1] condition"; labeljust=l; 4 [color=red, fontname=helvetica, label=TrivialLayout, shape=rectangle]; -5 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=dashed]; +5 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid]; 5 -> 4; -6 [color=black, fontname=helvetica, fontsize=10, label=target, shape=ellipse, style=dashed]; -6 -> 4; 1 -> 4; } -subgraph cluster_7 { +subgraph cluster_6 { fontname=helvetica; label="[2] "; labeljust=l; -8 [color=red, fontname=helvetica, label=FullAncillaAllocation, shape=rectangle]; -9 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=dashed]; -9 -> 8; -10 [color=black, fontname=helvetica, fontsize=10, label=target, shape=ellipse, style=dashed]; -10 -> 8; -4 -> 8; +7 [color=red, fontname=helvetica, label=FullAncillaAllocation, shape=rectangle]; +8 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid]; +8 -> 7; +4 -> 7; } -subgraph cluster_11 { +subgraph cluster_9 { fontname=helvetica; label="[3] "; labeljust=l; -12 [color=pink, fontname=helvetica, label=EnlargeWithAncilla, shape=rectangle]; -8 -> 12; +10 [color=pink, fontname=helvetica, label=EnlargeWithAncilla, shape=rectangle]; +7 -> 10; } -subgraph cluster_13 { +subgraph cluster_11 { fontname=helvetica; label="[4] "; labeljust=l; -14 [color=blue, fontname=helvetica, label=Unroller, shape=rectangle]; -15 [color=black, fontname=helvetica, fontsize=10, label=basis, shape=ellipse, style=dashed]; -15 -> 14; -16 [color=black, fontname=helvetica, fontsize=10, label=target, shape=ellipse, style=dashed]; -16 -> 14; -12 -> 14; +12 [color=blue, fontname=helvetica, label=Unroller, shape=rectangle]; +13 [color=black, fontname=helvetica, fontsize=10, label=basis, shape=ellipse, style=dashed]; +13 -> 12; +14 [color=black, fontname=helvetica, fontsize=10, label=target, shape=ellipse, style=dashed]; +14 -> 12; +10 -> 12; } -subgraph cluster_17 { +subgraph cluster_15 { fontname=helvetica; label="[5] "; labeljust=l; -18 [color=green, fontname=helvetica, label=CheckMap, shape=rectangle]; -19 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=dashed]; -19 -> 18; -20 [color=black, fontname=helvetica, fontsize=10, label=target, shape=ellipse, style=dashed]; -20 -> 18; -14 -> 18; +16 [color=green, fontname=helvetica, label=CheckMap, shape=rectangle]; +17 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=dashed]; +17 -> 16; +18 [color=black, fontname=helvetica, fontsize=10, label=target, shape=ellipse, style=dashed]; +18 -> 16; +12 -> 16; } -subgraph cluster_21 { +subgraph cluster_19 { fontname=helvetica; label="[6] do_while"; labeljust=l; -22 [color=blue, fontname=helvetica, label=BarrierBeforeFinalMeasurements, shape=rectangle]; -18 -> 22; +20 [color=blue, fontname=helvetica, label=BarrierBeforeFinalMeasurements, shape=rectangle]; +16 -> 20; } -subgraph cluster_23 { +subgraph cluster_21 { fontname=helvetica; label="[7] "; labeljust=l; -24 [color=blue, fontname=helvetica, label=CXDirection, shape=rectangle]; -25 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid]; -25 -> 24; -22 -> 24; +22 [color=blue, fontname=helvetica, label=CXDirection, shape=rectangle]; +23 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid]; +23 -> 22; +20 -> 22; } -subgraph cluster_26 { +subgraph cluster_24 { fontname=helvetica; label="[8] "; labeljust=l; -27 [color=grey, fontname=helvetica, label=RemoveResetInZeroState, shape=rectangle]; -24 -> 27; +25 [color=grey, fontname=helvetica, label=RemoveResetInZeroState, shape=rectangle]; +22 -> 25; } } From 12f3ae6ee806cc78b7dbe90aefbbd0b2622d4588 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 3 Apr 2023 12:10:34 -0400 Subject: [PATCH 11/14] Expand test coverage --- test/python/transpiler/test_check_map.py | 42 ++++++++++++++++++- .../test_full_ancilla_allocation.py | 38 ++++++++++++++++- test/python/transpiler/test_sabre_layout.py | 35 +++++++++++++++- test/python/transpiler/test_sabre_swap.py | 23 +++++++++- .../python/transpiler/test_stochastic_swap.py | 15 ++++++- 5 files changed, 148 insertions(+), 5 deletions(-) diff --git a/test/python/transpiler/test_check_map.py b/test/python/transpiler/test_check_map.py index e5d508da52ed..329eb7edc7cc 100644 --- a/test/python/transpiler/test_check_map.py +++ b/test/python/transpiler/test_check_map.py @@ -15,8 +15,9 @@ import unittest from qiskit import QuantumRegister, QuantumCircuit, ClassicalRegister +from qiskit.circuit.library import CXGate from qiskit.transpiler.passes import CheckMap -from qiskit.transpiler import CouplingMap +from qiskit.transpiler import CouplingMap, Target from qiskit.converters import circuit_to_dag from qiskit.test import QiskitTestCase @@ -43,6 +44,25 @@ def test_trivial_nop_map(self): pass_.run(dag) self.assertTrue(pass_.property_set["is_swap_mapped"]) + def test_trivial_nop_map_target(self): + """Trivial map in a circuit without entanglement + qr0:---[H]--- + + qr1:---[H]--- + + qr2:---[H]--- + + CouplingMap map: None + """ + qr = QuantumRegister(3, "qr") + circuit = QuantumCircuit(qr) + circuit.h(qr) + target = Target() + dag = circuit_to_dag(circuit) + pass_ = CheckMap(target=target) + pass_.run(dag) + self.assertTrue(pass_.property_set["is_swap_mapped"]) + def test_swap_mapped_true(self): """Mapped is easy to check qr0:--(+)-[H]-(+)- @@ -85,6 +105,26 @@ def test_swap_mapped_false(self): self.assertFalse(pass_.property_set["is_swap_mapped"]) + def test_swap_mapped_false_target(self): + """Needs [0]-[1] in a [0]--[2]--[1] + qr0:--(+)-- + | + qr1:---.--- + + CouplingMap map: [0]--[2]--[1] + """ + qr = QuantumRegister(2, "qr") + circuit = QuantumCircuit(qr) + circuit.cx(qr[0], qr[1]) + target = Target(num_qubits=2) + target.add_instruction(CXGate(), {(0, 2): None, (2, 1): None}) + dag = circuit_to_dag(circuit) + + pass_ = CheckMap(target=target) + pass_.run(dag) + + self.assertFalse(pass_.property_set["is_swap_mapped"]) + def test_swap_mapped_cf_true(self): """Check control flow blocks are mapped.""" num_qubits = 3 diff --git a/test/python/transpiler/test_full_ancilla_allocation.py b/test/python/transpiler/test_full_ancilla_allocation.py index ccf3e321f335..741b3b98a2b4 100644 --- a/test/python/transpiler/test_full_ancilla_allocation.py +++ b/test/python/transpiler/test_full_ancilla_allocation.py @@ -16,7 +16,8 @@ from qiskit.circuit import QuantumRegister, QuantumCircuit from qiskit.converters import circuit_to_dag -from qiskit.transpiler import CouplingMap, Layout +from qiskit.transpiler import CouplingMap, Layout, Target +from qiskit.circuit.library import CXGate from qiskit.transpiler.passes import FullAncillaAllocation from qiskit.test import QiskitTestCase from qiskit.transpiler.exceptions import TranspilerError @@ -61,6 +62,41 @@ def test_3q_circuit_5q_coupling(self): self.assertEqual(after_layout[3], ancilla[0]) self.assertEqual(after_layout[4], ancilla[1]) + def test_3q_circuit_5q_target(self): + """Allocates 2 ancillas for a 3q circuit in a 5q coupling map + + 0 -> q0 + q0 -> 0 1 -> q1 + q1 -> 1 => 2 -> q2 + q2 -> 2 3 -> ancilla0 + 4 -> ancilla1 + """ + target = Target(num_qubits=5) + target.add_instruction(CXGate(), {edge: None for edge in self.cmap5.get_edges()}) + + qr = QuantumRegister(3, "q") + circ = QuantumCircuit(qr) + dag = circuit_to_dag(circ) + + initial_layout = Layout() + initial_layout[0] = qr[0] + initial_layout[1] = qr[1] + initial_layout[2] = qr[2] + + pass_ = FullAncillaAllocation(target) + pass_.property_set["layout"] = initial_layout + + pass_.run(dag) + after_layout = pass_.property_set["layout"] + + ancilla = QuantumRegister(2, "ancilla") + + self.assertEqual(after_layout[0], qr[0]) + self.assertEqual(after_layout[1], qr[1]) + self.assertEqual(after_layout[2], qr[2]) + self.assertEqual(after_layout[3], ancilla[0]) + self.assertEqual(after_layout[4], ancilla[1]) + def test_3q_with_holes_5q_coupling(self): """Allocates 3 ancillas for a 2q circuit on a 5q coupling, with holes diff --git a/test/python/transpiler/test_sabre_layout.py b/test/python/transpiler/test_sabre_layout.py index 7ed0f69e768f..7a0ebbfba692 100644 --- a/test/python/transpiler/test_sabre_layout.py +++ b/test/python/transpiler/test_sabre_layout.py @@ -20,7 +20,7 @@ from qiskit.converters import circuit_to_dag from qiskit.test import QiskitTestCase from qiskit.compiler.transpiler import transpile -from qiskit.providers.fake_provider import FakeAlmaden +from qiskit.providers.fake_provider import FakeAlmaden, FakeAlmadenV2 from qiskit.providers.fake_provider import FakeKolkata from qiskit.providers.fake_provider import FakeMontreal @@ -93,6 +93,39 @@ def test_6q_circuit_20q_coupling(self): layout = pass_.property_set["layout"] self.assertEqual([layout[q] for q in circuit.qubits], [7, 8, 12, 6, 11, 13]) + def test_6q_circuit_20q_coupling_with_target(self): + """Test finds layout for 6q circuit on 20q device.""" + # ┌───┐┌───┐┌───┐┌───┐┌───┐ + # q0_0: ┤ X ├┤ X ├┤ X ├┤ X ├┤ X ├ + # └─┬─┘└─┬─┘└─┬─┘└─┬─┘└─┬─┘ + # q0_1: ──┼────■────┼────┼────┼── + # │ ┌───┐ │ │ │ + # q0_2: ──┼──┤ X ├──┼────■────┼── + # │ └───┘ │ │ + # q1_0: ──■─────────┼─────────┼── + # ┌───┐ │ │ + # q1_1: ─────┤ X ├──┼─────────■── + # └───┘ │ + # q1_2: ────────────■──────────── + qr0 = QuantumRegister(3, "q0") + qr1 = QuantumRegister(3, "q1") + circuit = QuantumCircuit(qr0, qr1) + circuit.cx(qr1[0], qr0[0]) + circuit.cx(qr0[1], qr0[0]) + circuit.cx(qr1[2], qr0[0]) + circuit.x(qr0[2]) + circuit.cx(qr0[2], qr0[0]) + circuit.x(qr1[1]) + circuit.cx(qr1[1], qr0[0]) + + dag = circuit_to_dag(circuit) + target = FakeAlmadenV2().target + pass_ = SabreLayout(target, seed=0, swap_trials=32, layout_trials=32) + pass_.run(dag) + + layout = pass_.property_set["layout"] + self.assertEqual([layout[q] for q in circuit.qubits], [7, 8, 12, 6, 11, 13]) + def test_layout_with_classical_bits(self): """Test sabre layout with classical bits recreate from issue #8635.""" qc = QuantumCircuit.from_qasm_str( diff --git a/test/python/transpiler/test_sabre_swap.py b/test/python/transpiler/test_sabre_swap.py index 0851789de49d..c3c039cbf77c 100644 --- a/test/python/transpiler/test_sabre_swap.py +++ b/test/python/transpiler/test_sabre_swap.py @@ -19,7 +19,7 @@ from qiskit.circuit.library import CCXGate, HGate, Measure, SwapGate from qiskit.converters import circuit_to_dag from qiskit.transpiler.passes import SabreSwap, TrivialLayout -from qiskit.transpiler import CouplingMap, PassManager +from qiskit.transpiler import CouplingMap, PassManager, Target from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit from qiskit.test import QiskitTestCase from qiskit.utils import optionals @@ -121,6 +121,27 @@ def test_trivial_case(self): self.assertEqual(new_qc, qc) + def test_trivial_with_target(self): + """Test that an already mapped circuit is unchanged with target.""" + coupling = CouplingMap.from_ring(5) + target = Target(num_qubits=5) + target.add_instruction(SwapGate(), {edge: None for edge in coupling.get_edges()}) + + qr = QuantumRegister(5, "q") + qc = QuantumCircuit(qr) + qc.cx(0, 1) # free + qc.cx(2, 3) # free + qc.h(0) # free + qc.cx(1, 2) # F + qc.cx(1, 0) + qc.cx(4, 3) # F + qc.cx(0, 4) + + passmanager = PassManager(SabreSwap(target, "basic")) + new_qc = passmanager.run(qc) + + self.assertEqual(new_qc, qc) + def test_lookahead_mode(self): """Test lookahead mode's lookahead finds single SWAP gate. ┌───┐ diff --git a/test/python/transpiler/test_stochastic_swap.py b/test/python/transpiler/test_stochastic_swap.py index 0b1cdfea1616..29ac362ac613 100644 --- a/test/python/transpiler/test_stochastic_swap.py +++ b/test/python/transpiler/test_stochastic_swap.py @@ -25,7 +25,7 @@ from qiskit.test import QiskitTestCase from qiskit.transpiler.passes.utils import CheckMap from qiskit.circuit.random import random_circuit -from qiskit.providers.fake_provider import FakeMumbai +from qiskit.providers.fake_provider import FakeMumbai, FakeMumbaiV2 from qiskit.compiler.transpiler import transpile from qiskit.circuit import ControlFlowOp, Clbit @@ -1268,6 +1268,19 @@ def test_random_circuit_no_control_flow(self, size): ) self.assert_valid_circuit(tqc) + @data(*range(1, 27)) + def test_random_circuit_no_control_flow_target(self, size): + """Test that transpiled random circuits without control flow are physical circuits.""" + circuit = random_circuit(size, 3, measure=True, seed=12342) + tqc = transpile( + circuit, + routing_method="stochastic", + layout_method="dense", + seed_transpiler=12342, + target=FakeMumbaiV2().target, + ) + self.assert_valid_circuit(tqc) + @data(*range(4, 27)) def test_random_circuit_for_loop(self, size): """Test that transpiled random circuits with nested for loops are physical circuits.""" From 9c8fbe32b077cd1ed2eef613d8cb94fac4436541 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 3 Apr 2023 12:27:41 -0400 Subject: [PATCH 12/14] Small typo fixes from review --- qiskit/transpiler/passes/layout/noise_adaptive_layout.py | 3 --- qiskit/transpiler/passes/layout/trivial_layout.py | 3 +-- .../notes/target-aware-layout-routing-2b39bd87a9f928e7.yaml | 4 ++-- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/qiskit/transpiler/passes/layout/noise_adaptive_layout.py b/qiskit/transpiler/passes/layout/noise_adaptive_layout.py index cfe08eba4dd2..8bee5a7b2913 100644 --- a/qiskit/transpiler/passes/layout/noise_adaptive_layout.py +++ b/qiskit/transpiler/passes/layout/noise_adaptive_layout.py @@ -60,9 +60,6 @@ def __init__(self, backend_prop): Args: backend_prop (Union[BackendProperties, Target]): 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 diff --git a/qiskit/transpiler/passes/layout/trivial_layout.py b/qiskit/transpiler/passes/layout/trivial_layout.py index 0517d6e756cd..544946c7ad9f 100644 --- a/qiskit/transpiler/passes/layout/trivial_layout.py +++ b/qiskit/transpiler/passes/layout/trivial_layout.py @@ -59,8 +59,7 @@ def run(self, dag): 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(): + 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( diff --git a/releasenotes/notes/target-aware-layout-routing-2b39bd87a9f928e7.yaml b/releasenotes/notes/target-aware-layout-routing-2b39bd87a9f928e7.yaml index 0fddf32a4b08..64eceb4d80e2 100644 --- a/releasenotes/notes/target-aware-layout-routing-2b39bd87a9f928e7.yaml +++ b/releasenotes/notes/target-aware-layout-routing-2b39bd87a9f928e7.yaml @@ -7,7 +7,7 @@ features: the constraints of a target backend. If the ``target`` keyword argument is specified it will be used as the source of truth for any hardware constraints used in the operation of the transpiler pass. It will - superscede any other arguments for specifying hardware constraints, + supersede any other arguments for specifying hardware constraints, typically those arguments which take a :class:`~.CouplingMap` or a :class:`~.BackendProperties` object. @@ -31,4 +31,4 @@ features: has a new keyword argument ``target`` which is used to specify a :class:`~.Target` object to model the constraints of the target backend being compiled for when generating a new :class:~.PassManager`. If specified this new argument will - superscede the other argument ``coupling_map``. + supersede the other argument ``coupling_map``. From 27434c317ffbeb4d30cd2a85c117d27c55523f71 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 3 Apr 2023 13:06:26 -0400 Subject: [PATCH 13/14] Fix lint and test failure --- qiskit/transpiler/passes/layout/trivial_layout.py | 2 +- test/python/transpiler/test_bip_mapping.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/transpiler/passes/layout/trivial_layout.py b/qiskit/transpiler/passes/layout/trivial_layout.py index 544946c7ad9f..5f1457d094bc 100644 --- a/qiskit/transpiler/passes/layout/trivial_layout.py +++ b/qiskit/transpiler/passes/layout/trivial_layout.py @@ -60,7 +60,7 @@ def run(self, dag): 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.") + 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 " diff --git a/test/python/transpiler/test_bip_mapping.py b/test/python/transpiler/test_bip_mapping.py index 86815f4fd895..60283abec750 100644 --- a/test/python/transpiler/test_bip_mapping.py +++ b/test/python/transpiler/test_bip_mapping.py @@ -139,7 +139,7 @@ def test_can_map_measurements_correctly_with_target(self): circuit.measure(qr[1], cr[0]) circuit.measure(qr[2], cr[1]) - actual = BIPMapping(target=target)(circuit) + actual = BIPMapping(target)(circuit) q = QuantumRegister(3, "q") expected = QuantumCircuit(q, cr) From 5e2dc2358c3cd077f38c48cc1c3b75aa68a7d9a3 Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Tue, 4 Apr 2023 07:11:54 -0400 Subject: [PATCH 14/14] add swap scoring based on non-uniform cost of swaps to sabre --- crates/accelerate/src/sabre_swap/mod.rs | 1 + qiskit/transpiler/coupling.py | 5 +- .../transpiler/passes/layout/sabre_layout.py | 47 +++++++++++++++++++ .../transpiler/preset_passmanagers/level0.py | 6 +-- 4 files changed, 54 insertions(+), 5 deletions(-) diff --git a/crates/accelerate/src/sabre_swap/mod.rs b/crates/accelerate/src/sabre_swap/mod.rs index 0be06dca1591..37a184b31774 100644 --- a/crates/accelerate/src/sabre_swap/mod.rs +++ b/crates/accelerate/src/sabre_swap/mod.rs @@ -556,6 +556,7 @@ fn choose_best_swap( } else if (score - min_score).abs() < BEST_EPSILON { best_swaps.push(swap); } + //println!("swap: {}-{}: {}", swap[0], swap[1], score); } *best_swaps.choose(rng).unwrap() } diff --git a/qiskit/transpiler/coupling.py b/qiskit/transpiler/coupling.py index a15d142cf544..6606bb7aeffe 100644 --- a/qiskit/transpiler/coupling.py +++ b/qiskit/transpiler/coupling.py @@ -254,9 +254,10 @@ def make_symmetric(self): # 0.13.0 is released. edges = self.get_edges() edge_set = set(edges) - for src, dest in edges: + for i in range(len(edge_set)): + src, dest, weight = self.graph.edge_index_map()[i] if (dest, src) not in edge_set: - self.graph.add_edge(dest, src, None) + self.graph.add_edge(dest, src, weight) self._dist_matrix = None # invalidate self._is_symmetric = None # invalidate diff --git a/qiskit/transpiler/passes/layout/sabre_layout.py b/qiskit/transpiler/passes/layout/sabre_layout.py index 26db9ddecb43..806bcf247992 100644 --- a/qiskit/transpiler/passes/layout/sabre_layout.py +++ b/qiskit/transpiler/passes/layout/sabre_layout.py @@ -35,6 +35,7 @@ ) from qiskit.transpiler.passes.routing.sabre_swap import process_swaps, apply_gate from qiskit.transpiler.target import Target +from qiskit.quantum_info.synthesis.two_qubit_decompose import TwoQubitWeylDecomposition from qiskit.tools.parallel import CPU_COUNT logger = logging.getLogger(__name__) @@ -215,7 +216,18 @@ def run(self, dag): self.property_set["layout"] = initial_layout self.routing_pass.fake_run = False return dag + dist_matrix = self.coupling_map.distance_matrix + print('original distance matrix') + print(dist_matrix) + + if self.target: + dist_matrix = rx.digraph_floyd_warshall_numpy( + self.coupling_map.graph, weight_fn=lambda x: self._swap_cost(x) + ) + print('new distance matrix') + print(dist_matrix) + original_qubit_indices = {bit: index for index, bit in enumerate(dag.qubits)} original_clbit_indices = {bit: index for index, bit in enumerate(dag.clbits)} @@ -327,3 +339,38 @@ def _compose_layouts(self, initial_layout, pass_final_layout, qregs): qubit_map = Layout.combine_into_edge_map(initial_layout, trivial_layout) final_layout = {v: pass_final_layout._v2p[qubit_map[v]] for v in initial_layout._v2p} return Layout(final_layout) + + def _swap_cost(self, instructions): + """Get the minimum cost of a SWAP on a pair of physical qubits given a list + of available instructions on those qubits. + + TODO: this function only considers certain known basis for which Qiskit knows + analytical decompositions. This can be generalized to arbitrary basis using the + tools of monodromy polytopes. + + Args: + instructions (list[dict]): mappings of gate name to properties for that gate. + + Return: + float: minimum cost of performing SWAP using the available physical instructions. + """ + swap_cost = np.inf + for key, value in instructions.items(): + weyl = TwoQubitWeylDecomposition(self.target.operation_from_name(key)) + a, b, c = weyl.a, weyl.b, weyl.c + if a == b == c == np.pi / 4: # 1 x SWAP + gates_per_swap = 1 + elif b == c == 0: # k x controlled XX(theta) + gates_per_swap = 3 * (np.pi / 2 / (2 * a)) + elif a == np.pi / 4 and c == 0: # 3 x supercontrolled + gates_per_swap = 3 + elif a == b == abs(c) == np.pi / 8: # 2 x sqrt(SWAP) + gates_per_swap = 2 + elif a == b == np.pi / 8 and c == 0: # 2 x sqrt(iSWAP) + gates_per_swap = 3 + elif (a, b, c) == (np.pi / 4, np.pi / 8, 0): # 2 x B + gates_per_swap = 2 + + if gates_per_swap * value.error < swap_cost: + swap_cost = gates_per_swap * value.error + return swap_cost diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index 920e314825c1..4ba3021ca717 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -77,10 +77,10 @@ def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa def _choose_layout_condition(property_set): return not property_set["layout"] - if coupling_map is None: - coupling_map_layout = target - else: + if target is None: coupling_map_layout = coupling_map + else: + coupling_map_layout = target if layout_method == "trivial": _choose_layout = TrivialLayout(coupling_map_layout)