diff --git a/qiskit/compiler/transpile.py b/qiskit/compiler/transpile.py index 0b6bfdc9e087..d5f048d01ea8 100644 --- a/qiskit/compiler/transpile.py +++ b/qiskit/compiler/transpile.py @@ -13,6 +13,7 @@ # that they have been altered from the originals. """Circuit transpile function""" +import warnings from typing import List, Union, Dict, Callable, Any, Optional, Tuple from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.providers import BaseBackend @@ -40,6 +41,8 @@ def transpile(circuits: Union[QuantumCircuit, List[QuantumCircuit]], coupling_map: Optional[Union[CouplingMap, List[List[int]]]] = None, backend_properties: Optional[BackendProperties] = None, initial_layout: Optional[Union[Layout, Dict, List]] = None, + layout_method: Optional[str] = None, + routing_method: Optional[str] = None, seed_transpiler: Optional[int] = None, optimization_level: Optional[int] = None, pass_manager: Optional[PassManager] = None, @@ -108,6 +111,10 @@ def transpile(circuits: Union[QuantumCircuit, List[QuantumCircuit]], [qr[0], None, None, qr[1], None, qr[2]] + layout_method: Name of layout selection pass ('trivial', 'dense', 'noise_adaptive') + Sometimes a perfect layout can be available in which case the layout_method + may not run. + routing_method: Name of routing pass ('basic', 'lookahead', 'stochastic') seed_transpiler: Sets random seed for the stochastic parts of the transpiler optimization_level: How much optimization to perform on the circuits. Higher levels generate more optimized circuits, @@ -170,6 +177,7 @@ def callback_func(**kwargs): circuits = circuits if isinstance(circuits, list) else [circuits] transpile_args = _parse_transpile_args(circuits, backend, basis_gates, coupling_map, backend_properties, initial_layout, + layout_method, routing_method, seed_transpiler, optimization_level, pass_manager, callback, output_name) # Check circuit width against number of qubits in coupling_map(s) @@ -263,7 +271,8 @@ def _transpile_circuit(circuit_config_tuple: Tuple[QuantumCircuit, Dict]) -> Qua def _parse_transpile_args(circuits, backend, basis_gates, coupling_map, backend_properties, - initial_layout, seed_transpiler, optimization_level, + initial_layout, layout_method, routing_method, + seed_transpiler, optimization_level, pass_manager, callback, output_name) -> List[Dict]: """Resolve the various types of args allowed to the transpile() function through duck typing, overriding args, etc. Refer to the transpile() docstring for details on @@ -276,6 +285,9 @@ def _parse_transpile_args(circuits, backend, Returns: list[dicts]: a list of transpile parameters. """ + if initial_layout is not None and layout_method is not None: + warnings.warn("initial_layout provided; layout_method is ignored.", + UserWarning) # Each arg could be single or a list. If list, it must be the same size as # number of circuits. If single, duplicate to create a list of that size. num_circuits = len(circuits) @@ -284,6 +296,8 @@ def _parse_transpile_args(circuits, backend, coupling_map = _parse_coupling_map(coupling_map, backend, num_circuits) backend_properties = _parse_backend_properties(backend_properties, backend, num_circuits) initial_layout = _parse_initial_layout(initial_layout, circuits) + layout_method = _parse_layout_method(layout_method, num_circuits) + routing_method = _parse_routing_method(routing_method, num_circuits) seed_transpiler = _parse_seed_transpiler(seed_transpiler, num_circuits) optimization_level = _parse_optimization_level(optimization_level, num_circuits) pass_manager = _parse_pass_manager(pass_manager, num_circuits) @@ -292,17 +306,20 @@ def _parse_transpile_args(circuits, backend, list_transpile_args = [] for args in zip(basis_gates, coupling_map, backend_properties, - initial_layout, seed_transpiler, optimization_level, + initial_layout, layout_method, routing_method, + seed_transpiler, optimization_level, pass_manager, output_name, callback): transpile_args = {'pass_manager_config': PassManagerConfig(basis_gates=args[0], coupling_map=args[1], backend_properties=args[2], initial_layout=args[3], - seed_transpiler=args[4]), - 'optimization_level': args[5], - 'pass_manager': args[6], - 'output_name': args[7], - 'callback': args[8]} + layout_method=args[4], + routing_method=args[5], + seed_transpiler=args[6]), + 'optimization_level': args[7], + 'pass_manager': args[8], + 'output_name': args[9], + 'callback': args[10]} list_transpile_args.append(transpile_args) return list_transpile_args @@ -388,6 +405,18 @@ def _layout_from_raw(initial_layout, circuit): return initial_layout +def _parse_layout_method(layout_method, num_circuits): + if not isinstance(layout_method, list): + layout_method = [layout_method] * num_circuits + return layout_method + + +def _parse_routing_method(routing_method, num_circuits): + if not isinstance(routing_method, list): + routing_method = [routing_method] * num_circuits + return routing_method + + def _parse_seed_transpiler(seed_transpiler, num_circuits): if not isinstance(seed_transpiler, list): seed_transpiler = [seed_transpiler] * num_circuits diff --git a/qiskit/transpiler/pass_manager_config.py b/qiskit/transpiler/pass_manager_config.py index 4848207e98cc..392d5ac09b6f 100644 --- a/qiskit/transpiler/pass_manager_config.py +++ b/qiskit/transpiler/pass_manager_config.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2017, 2019. +# (C) Copyright IBM 2017, 2020. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -17,24 +17,14 @@ class PassManagerConfig: """Pass Manager Configuration. - - Attributes: - initial_layout (Layout): Initial position of virtual qubits on physical - qubits. - basis_gates (list): List of basis gate names to unroll to. - coupling_map (CouplingMap): Directed graph represented a coupling map. - backend_properties (BackendProperties): Properties returned by a - backend, including - information on gate errors, readout errors, qubit coherence times, - etc. - seed_transpiler (int): Sets random seed for the stochastic parts of the - transpiler. """ def __init__(self, initial_layout=None, basis_gates=None, coupling_map=None, + layout_method=None, + routing_method=None, backend_properties=None, seed_transpiler=None): """Initialize a PassManagerConfig object @@ -45,6 +35,10 @@ def __init__(self, basis_gates (list): List of basis gate names to unroll to. coupling_map (CouplingMap): Directed graph represented a coupling map. + layout_method (str): the pass to use for choosing initial qubit + placement. + routing_method (str): the pass to use for routing qubits on the + architecture. backend_properties (BackendProperties): Properties returned by a backend, including information on gate errors, readout errors, qubit coherence times, etc. @@ -54,5 +48,7 @@ def __init__(self, self.initial_layout = initial_layout self.basis_gates = basis_gates self.coupling_map = coupling_map + self.layout_method = layout_method + self.routing_method = routing_method self.backend_properties = backend_properties self.seed_transpiler = seed_transpiler diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index 53e024df7669..688774b92c6d 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -26,7 +26,11 @@ from qiskit.transpiler.passes import CXDirection from qiskit.transpiler.passes import SetLayout from qiskit.transpiler.passes import TrivialLayout +from qiskit.transpiler.passes import DenseLayout +from qiskit.transpiler.passes import NoiseAdaptiveLayout from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements +from qiskit.transpiler.passes import BasicSwap +from qiskit.transpiler.passes import LookaheadSwap from qiskit.transpiler.passes import StochasticSwap from qiskit.transpiler.passes import FullAncillaAllocation from qiskit.transpiler.passes import EnlargeWithAncilla @@ -34,6 +38,8 @@ from qiskit.transpiler.passes import ApplyLayout from qiskit.transpiler.passes import CheckCXDirection +from qiskit.transpiler import TranspilerError + def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: """Level 0 pass manager: no explicit optimization other than mapping to backend. @@ -54,19 +60,32 @@ def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: Returns: a level 0 pass manager. + + Raises: + TranspilerError: if the passmanager config is invalid. """ basis_gates = pass_manager_config.basis_gates coupling_map = pass_manager_config.coupling_map initial_layout = pass_manager_config.initial_layout + layout_method = pass_manager_config.layout_method or 'trivial' + routing_method = pass_manager_config.routing_method or 'stochastic' seed_transpiler = pass_manager_config.seed_transpiler + backend_properties = pass_manager_config.backend_properties - # 1. Use trivial layout if no layout given + # 1. Choose an initial layout if not set by user (default: trivial layout) _given_layout = SetLayout(initial_layout) def _choose_layout_condition(property_set): return not property_set['layout'] - _choose_layout = TrivialLayout(coupling_map) + if layout_method == 'trivial': + _choose_layout = TrivialLayout(coupling_map) + elif layout_method == 'dense': + _choose_layout = DenseLayout(coupling_map, backend_properties) + elif layout_method == 'noise_adaptive': + _choose_layout = NoiseAdaptiveLayout(backend_properties) + else: + raise TranspilerError("Invalid layout method %s." % layout_method) # 2. Extend dag/layout with ancillas using the full coupling map _embed = [FullAncillaAllocation(coupling_map), EnlargeWithAncilla(), ApplyLayout()] @@ -80,8 +99,15 @@ def _choose_layout_condition(property_set): def _swap_condition(property_set): return not property_set['is_swap_mapped'] - _swap = [BarrierBeforeFinalMeasurements(), - StochasticSwap(coupling_map, trials=20, seed=seed_transpiler)] + _swap = [BarrierBeforeFinalMeasurements()] + if routing_method == 'basic': + _swap += [BasicSwap(coupling_map)] + elif routing_method == 'stochastic': + _swap += [StochasticSwap(coupling_map, trials=20, seed=seed_transpiler)] + elif routing_method == 'lookahead': + _swap += [LookaheadSwap(coupling_map, search_depth=2, search_width=2)] + else: + raise TranspilerError("Invalid routing method %s." % routing_method) # 5. Unroll to the basis _unroll = Unroller(basis_gates) diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index 9b05eeb318a5..1cfc9a721365 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -27,7 +27,11 @@ from qiskit.transpiler.passes import CXDirection from qiskit.transpiler.passes import SetLayout from qiskit.transpiler.passes import TrivialLayout +from qiskit.transpiler.passes import DenseLayout +from qiskit.transpiler.passes import NoiseAdaptiveLayout from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements +from qiskit.transpiler.passes import BasicSwap +from qiskit.transpiler.passes import LookaheadSwap from qiskit.transpiler.passes import StochasticSwap from qiskit.transpiler.passes import FullAncillaAllocation from qiskit.transpiler.passes import EnlargeWithAncilla @@ -38,16 +42,18 @@ from qiskit.transpiler.passes import ApplyLayout from qiskit.transpiler.passes import CheckCXDirection from qiskit.transpiler.passes import Layout2qDistance -from qiskit.transpiler.passes import DenseLayout + +from qiskit.transpiler import TranspilerError def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: """Level 1 pass manager: light optimization by simple adjacent gate collapsing. - This pass manager applies the user-given initial layout. If none is given, and a trivial - layout (i-th virtual -> i-th physical) makes the circuit fit the coupling map, that is used. - Otherwise, the circuit is mapped to the most densely connected coupling subgraph, and swaps - are inserted to map. Any unused physical qubit is allocated as ancilla space. + This pass manager applies the user-given initial layout. If none is given, + and a trivial layout (i-th virtual -> i-th physical) makes the circuit fit + the coupling map, that is used. + Otherwise, the circuit is mapped to the most densely connected coupling subgraph, + and swaps are inserted to map. Any unused physical qubit is allocated as ancilla space. The pass manager then unrolls the circuit to the desired basis, and transforms the circuit to match the coupling map. Finally, optimizations in the form of adjacent gate collapse and redundant reset removal are performed. @@ -61,10 +67,15 @@ def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: Returns: a level 1 pass manager. + + Raises: + TranspilerError: if the passmanager config is invalid. """ basis_gates = pass_manager_config.basis_gates coupling_map = pass_manager_config.coupling_map initial_layout = pass_manager_config.initial_layout + layout_method = pass_manager_config.layout_method or 'dense' + routing_method = pass_manager_config.routing_method or 'stochastic' seed_transpiler = pass_manager_config.seed_transpiler backend_properties = pass_manager_config.backend_properties @@ -79,7 +90,14 @@ def _choose_layout_condition(property_set): return not property_set['layout'] # 2. Use a better layout on densely connected qubits, if circuit needs swaps - _improve_layout = DenseLayout(coupling_map, backend_properties) + if layout_method == 'trivial': + _improve_layout = TrivialLayout(coupling_map) + elif layout_method == 'dense': + _improve_layout = DenseLayout(coupling_map, backend_properties) + elif layout_method == 'noise_adaptive': + _improve_layout = NoiseAdaptiveLayout(backend_properties) + else: + raise TranspilerError("Invalid layout method %s." % layout_method) def _not_perfect_yet(property_set): return property_set['trivial_layout_score'] is not None and \ @@ -97,8 +115,15 @@ def _not_perfect_yet(property_set): def _swap_condition(property_set): return not property_set['is_swap_mapped'] - _swap = [BarrierBeforeFinalMeasurements(), - StochasticSwap(coupling_map, trials=20, seed=seed_transpiler)] + _swap = [BarrierBeforeFinalMeasurements()] + if routing_method == 'basic': + _swap += [BasicSwap(coupling_map)] + elif routing_method == 'stochastic': + _swap += [StochasticSwap(coupling_map, trials=50, seed=seed_transpiler)] + elif routing_method == 'lookahead': + _swap += [LookaheadSwap(coupling_map, search_depth=4, search_width=4)] + else: + raise TranspilerError("Invalid routing method %s." % routing_method) # 6. Unroll to the basis _unroll = Unroller(basis_gates) diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index 09e161628d2f..ea25f5ae442e 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -26,9 +26,13 @@ from qiskit.transpiler.passes import CheckMap from qiskit.transpiler.passes import CXDirection from qiskit.transpiler.passes import SetLayout -from qiskit.transpiler.passes import DenseLayout from qiskit.transpiler.passes import CSPLayout +from qiskit.transpiler.passes import TrivialLayout +from qiskit.transpiler.passes import DenseLayout +from qiskit.transpiler.passes import NoiseAdaptiveLayout from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements +from qiskit.transpiler.passes import BasicSwap +from qiskit.transpiler.passes import LookaheadSwap from qiskit.transpiler.passes import StochasticSwap from qiskit.transpiler.passes import FullAncillaAllocation from qiskit.transpiler.passes import EnlargeWithAncilla @@ -40,6 +44,8 @@ from qiskit.transpiler.passes import ApplyLayout from qiskit.transpiler.passes import CheckCXDirection +from qiskit.transpiler import TranspilerError + def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: """Level 2 pass manager: medium optimization by initial layout selection and @@ -64,10 +70,15 @@ def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: Returns: a level 2 pass manager. + + Raises: + TranspilerError: if the passmanager config is invalid. """ basis_gates = pass_manager_config.basis_gates coupling_map = pass_manager_config.coupling_map initial_layout = pass_manager_config.initial_layout + layout_method = pass_manager_config.layout_method or 'dense' + routing_method = pass_manager_config.routing_method or 'stochastic' seed_transpiler = pass_manager_config.seed_transpiler backend_properties = pass_manager_config.backend_properties @@ -78,7 +89,14 @@ def _choose_layout_condition(property_set): return not property_set['layout'] _choose_layout_1 = CSPLayout(coupling_map, call_limit=1000, time_limit=10) - _choose_layout_2 = DenseLayout(coupling_map, backend_properties) + if layout_method == 'trivial': + _choose_layout_2 = TrivialLayout(coupling_map) + elif layout_method == 'dense': + _choose_layout_2 = DenseLayout(coupling_map, backend_properties) + elif layout_method == 'noise_adaptive': + _choose_layout_2 = NoiseAdaptiveLayout(backend_properties) + else: + raise TranspilerError("Invalid layout method %s." % layout_method) # 2. Extend dag/layout with ancillas using the full coupling map _embed = [FullAncillaAllocation(coupling_map), EnlargeWithAncilla(), ApplyLayout()] @@ -92,8 +110,15 @@ def _choose_layout_condition(property_set): def _swap_condition(property_set): return not property_set['is_swap_mapped'] - _swap = [BarrierBeforeFinalMeasurements(), - StochasticSwap(coupling_map, trials=20, seed=seed_transpiler)] + _swap = [BarrierBeforeFinalMeasurements()] + if routing_method == 'basic': + _swap += [BasicSwap(coupling_map)] + elif routing_method == 'stochastic': + _swap += [StochasticSwap(coupling_map, trials=100, seed=seed_transpiler)] + elif routing_method == 'lookahead': + _swap += [LookaheadSwap(coupling_map, search_depth=5, search_width=5)] + else: + raise TranspilerError("Invalid routing method %s." % routing_method) # 5. Unroll to the basis _unroll = Unroller(basis_gates) diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index 994bc4c8fdf1..6b7f6a490456 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -27,9 +27,13 @@ from qiskit.transpiler.passes import CXDirection from qiskit.transpiler.passes import SetLayout from qiskit.transpiler.passes import CSPLayout +from qiskit.transpiler.passes import TrivialLayout from qiskit.transpiler.passes import DenseLayout -from qiskit.transpiler.passes import StochasticSwap +from qiskit.transpiler.passes import NoiseAdaptiveLayout from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements +from qiskit.transpiler.passes import BasicSwap +from qiskit.transpiler.passes import LookaheadSwap +from qiskit.transpiler.passes import StochasticSwap from qiskit.transpiler.passes import FullAncillaAllocation from qiskit.transpiler.passes import EnlargeWithAncilla from qiskit.transpiler.passes import FixedPoint @@ -44,6 +48,8 @@ from qiskit.transpiler.passes import ApplyLayout from qiskit.transpiler.passes import CheckCXDirection +from qiskit.transpiler import TranspilerError + def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: """Level 3 pass manager: heavy optimization by noise adaptive qubit mapping and @@ -68,10 +74,15 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: Returns: a level 3 pass manager. + + Raises: + TranspilerError: if the passmanager config is invalid. """ basis_gates = pass_manager_config.basis_gates coupling_map = pass_manager_config.coupling_map initial_layout = pass_manager_config.initial_layout + layout_method = pass_manager_config.layout_method or 'dense' + routing_method = pass_manager_config.routing_method or 'stochastic' seed_transpiler = pass_manager_config.seed_transpiler backend_properties = pass_manager_config.backend_properties @@ -85,8 +96,14 @@ def _choose_layout_condition(property_set): return not property_set['layout'] _choose_layout_1 = CSPLayout(coupling_map, call_limit=10000, time_limit=60) - # TODO: benchmark DenseLayout vs. NoiseAdaptiveLayout in terms of noise aware mapping - _choose_layout_2 = DenseLayout(coupling_map, backend_properties) + if layout_method == 'trivial': + _choose_layout_2 = TrivialLayout(coupling_map) + elif layout_method == 'dense': + _choose_layout_2 = DenseLayout(coupling_map, backend_properties) + elif layout_method == 'noise_adaptive': + _choose_layout_2 = NoiseAdaptiveLayout(backend_properties) + else: + raise TranspilerError("Invalid layout method %s." % layout_method) # 3. Extend dag/layout with ancillas using the full coupling map _embed = [FullAncillaAllocation(coupling_map), EnlargeWithAncilla(), ApplyLayout()] @@ -97,9 +114,15 @@ def _choose_layout_condition(property_set): def _swap_condition(property_set): return not property_set['is_swap_mapped'] - _swap = [BarrierBeforeFinalMeasurements(), - Unroll3qOrMore(), - StochasticSwap(coupling_map, trials=20, seed=seed_transpiler)] + _swap = [BarrierBeforeFinalMeasurements(), Unroll3qOrMore()] + if routing_method == 'basic': + _swap += [BasicSwap(coupling_map)] + elif routing_method == 'stochastic': + _swap += [StochasticSwap(coupling_map, trials=200, seed=seed_transpiler)] + elif routing_method == 'lookahead': + _swap += [LookaheadSwap(coupling_map, search_depth=5, search_width=6)] + else: + raise TranspilerError("Invalid routing method %s." % routing_method) # 5. 1q rotation merge and commutative cancellation iteratively until no more change in depth _depth_check = [Depth(), FixedPoint('depth')] diff --git a/releasenotes/notes/transpile-method-selectors-1c426457743a6fa7.yaml b/releasenotes/notes/transpile-method-selectors-1c426457743a6fa7.yaml new file mode 100644 index 000000000000..8acec639d927 --- /dev/null +++ b/releasenotes/notes/transpile-method-selectors-1c426457743a6fa7.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + ``qiskit.transpile()`` now allows you to pass ``layout_method`` + and ``routing_method`` to select a particular method for placement and + routing of circuits on constrained architectures. e.g. + ``transpile(circ, backend, layout_method='dense', routing_method='lookahead')``