Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

layout_method and routing_method selectors in transpile() #3999

Merged
merged 7 commits into from
Mar 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 36 additions & 7 deletions qiskit/compiler/transpile.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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')
kdk marked this conversation as resolved.
Show resolved Hide resolved
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,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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],
ajavadia marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down Expand Up @@ -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
kdk marked this conversation as resolved.
Show resolved Hide resolved


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
Expand Down
22 changes: 9 additions & 13 deletions qiskit/transpiler/pass_manager_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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.
Expand All @@ -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
34 changes: 30 additions & 4 deletions qiskit/transpiler/preset_passmanagers/level0.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,20 @@
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
from qiskit.transpiler.passes import RemoveResetInZeroState
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.
Expand All @@ -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':
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be consolidated in e.g. _parse_layout_method (and pass the selected class or None in the passmanagerconfig?)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah that would be good to avoid repetition but right now the layout passes don't have the same signature, so I can't just pass the class here.

    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)

It would be ideal to provide a unified interface though. I think in general there is too much repetition between the preset passmanagers, so a refactoring that makes them derive the same base and tweak would be great.

_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()]
Expand All @@ -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)
Expand Down
41 changes: 33 additions & 8 deletions qiskit/transpiler/preset_passmanagers/level1.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand All @@ -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

Expand All @@ -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 \
Expand All @@ -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)
Expand Down
33 changes: 29 additions & 4 deletions qiskit/transpiler/preset_passmanagers/level2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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

Expand All @@ -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()]
Expand All @@ -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)
Expand Down
Loading