diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py index 152ce63ac130..08cee7f53d75 100644 --- a/qiskit/transpiler/passes/__init__.py +++ b/qiskit/transpiler/passes/__init__.py @@ -30,6 +30,7 @@ SabreLayout CSPLayout ApplyLayout + ApplyLayoutSwap Layout2qDistance EnlargeWithAncilla FullAncillaAllocation @@ -130,6 +131,7 @@ from .layout import SabreLayout from .layout import CSPLayout from .layout import ApplyLayout +from .layout import ApplyLayoutSwaps from .layout import Layout2qDistance from .layout import EnlargeWithAncilla from .layout import FullAncillaAllocation diff --git a/qiskit/transpiler/passes/layout/__init__.py b/qiskit/transpiler/passes/layout/__init__.py index 1afcf0f62d6a..2d5892ed3dad 100644 --- a/qiskit/transpiler/passes/layout/__init__.py +++ b/qiskit/transpiler/passes/layout/__init__.py @@ -19,6 +19,7 @@ from .sabre_layout import SabreLayout from .csp_layout import CSPLayout from .apply_layout import ApplyLayout +from .apply_layout_swap import ApplyLayoutSwaps from .layout_2q_distance import Layout2qDistance from .enlarge_with_ancilla import EnlargeWithAncilla from .full_ancilla_allocation import FullAncillaAllocation diff --git a/qiskit/transpiler/passes/layout/apply_layout.py b/qiskit/transpiler/passes/layout/apply_layout.py index 6d032b23d007..c5830ad8d340 100644 --- a/qiskit/transpiler/passes/layout/apply_layout.py +++ b/qiskit/transpiler/passes/layout/apply_layout.py @@ -39,7 +39,7 @@ def run(self, dag): Raises: TranspilerError: if no layout is found in `property_set` or no full physical qubits. """ - layout = self.property_set["layout"] + layout = self.property_set["layout_out"] = self.property_set["layout"] if not layout: raise TranspilerError( "No 'layout' is found in property_set. Please run a Layout pass in advance.") diff --git a/qiskit/transpiler/passes/layout/apply_layout_swap.py b/qiskit/transpiler/passes/layout/apply_layout_swap.py new file mode 100644 index 000000000000..e47631ede1f4 --- /dev/null +++ b/qiskit/transpiler/passes/layout/apply_layout_swap.py @@ -0,0 +1,89 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# 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 +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""TODO""" +from typing import Union + +import numpy as np + +from qiskit.transpiler import Layout, CouplingMap + +from qiskit.circuit import QuantumRegister +from qiskit.dagcircuit import DAGCircuit +from qiskit.transpiler.basepasses import TransformationPass +from qiskit.transpiler.exceptions import TranspilerError +from qiskit.transpiler.passes.routing.layout_transformation import LayoutTransformation +from qiskit.transpiler.passes.routing.algorithms import ApproximateTokenSwapper + + +class ApplyLayoutSwaps(TransformationPass): + """TODO""" + def __init__(self, coupling_map: CouplingMap, + seed: Union[int, np.random.default_rng] = None, + trials: int = 4): + super().__init__() + self.coupling_map = coupling_map + self.seed = seed + self.trials = trials + + def run(self, dag): + """Run the ApplyLayout pass on `dag`. + + Args: + dag (DAGCircuit): DAG to map. + + Returns: + DAGCircuit: A mapped DAG (with physical qubits). + + Raises: + TranspilerError: if no layout is found in `property_set` or no full physical qubits. + """ + layout = self.property_set["layout"] + if not layout: + raise TranspilerError( + "No 'layout' is found in property_set. Please run a Layout pass in advance.") + if len(layout) != (1 + max(layout.get_physical_bits())): + raise TranspilerError( + "The 'layout' must be full (with ancilla).") + + if self.coupling_map: + graph = self.coupling_map.graph.to_undirected() + else: + coupling_map = CouplingMap.from_full(len(layout)) + graph = coupling_map.graph + + token_swapper = ApproximateTokenSwapper(graph, self.seed) + + q = QuantumRegister(len(layout), 'q') + + new_dag = DAGCircuit() + new_dag.add_qreg(q) + new_dag.metadata = dag.metadata + new_dag._global_phase = dag._global_phase + for creg in dag.cregs.values(): + new_dag.add_creg(creg) + + trivial_layout = Layout.generate_trivial_layout(*dag.qregs.values()) + # Find the permutation from trivial layout to property_set['layout']. + permutation = {pqubit: layout.get_virtual_bits()[vqubit] + for vqubit, pqubit in trivial_layout.get_virtual_bits().items()} + perm_circ = token_swapper.permutation_circuit(permutation, self.trials) + + qubits = [dag.qubits[i[0]] for i in sorted(perm_circ.inputmap.items(), key=lambda x: x[0])] + new_dag.compose(perm_circ.circuit, qubits=qubits) + + for node in dag.topological_op_nodes(): + if node.type == 'op': + qargs = [q[layout[qarg]] for qarg in node.qargs] + new_dag.apply_operation_back(node.op, qargs, node.cargs) + + return new_dag diff --git a/qiskit/transpiler/passes/routing/basic_swap.py b/qiskit/transpiler/passes/routing/basic_swap.py index 2641163d0d84..6fa923c5710f 100644 --- a/qiskit/transpiler/passes/routing/basic_swap.py +++ b/qiskit/transpiler/passes/routing/basic_swap.py @@ -58,8 +58,7 @@ def run(self, dag): raise TranspilerError('The layout does not match the amount of qubits in the DAG') canonical_register = dag.qregs['q'] - trivial_layout = Layout.generate_trivial_layout(canonical_register) - current_layout = trivial_layout.copy() + current_layout = Layout.generate_trivial_layout(canonical_register) for layer in dag.serial_layers(): subdag = layer['graph'] @@ -95,5 +94,6 @@ def run(self, dag): order = current_layout.reorder_bits(new_dag.qubits) new_dag.compose(subdag, qubits=order) + self.property_set['layout_out'] = current_layout return new_dag diff --git a/qiskit/transpiler/passes/routing/layout_transformation.py b/qiskit/transpiler/passes/routing/layout_transformation.py index 2d74dd8d9426..45dea0312e40 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], + to_layout: Union[Layout, str] = None, seed: Union[int, np.random.default_rng] = None, - trials=4): + trials: int = 4): """LayoutTransformation initializer. Args: @@ -42,10 +42,12 @@ def __init__(self, coupling_map: CouplingMap, from_layout (Union[Layout, str]): The starting layout of qubits onto physical qubits. If the type is str, look up `property_set` when this pass runs. + If None, it will map to the trivial layout. to_layout (Union[Layout, str]): The final layout of qubits on physical qubits. If the type is str, look up `property_set` when this pass runs. + if None, use trivial. seed (Union[int, np.random.default_rng]): Seed to use for random trials. @@ -61,7 +63,7 @@ def __init__(self, coupling_map: CouplingMap, graph = coupling_map.graph.to_undirected() else: self.coupling_map = CouplingMap.from_full(len(to_layout)) - graph = self.coupling_map.graph.to_undirected() + graph = self.coupling_map.graph self.token_swapper = ApproximateTokenSwapper(graph, seed) self.trials = trials @@ -86,24 +88,31 @@ def run(self, dag): from_layout = self.from_layout if isinstance(from_layout, str): - try: - from_layout = self.property_set[from_layout] - except Exception: - raise TranspilerError('No {} (from_layout) in property_set.'.format(from_layout)) + if self.property_set[from_layout] is None: + raise TranspilerError('No property_set["{}"] (from_layout).'.format(from_layout)) + from_layout = self.property_set[from_layout] to_layout = self.to_layout - if isinstance(to_layout, str): - try: - to_layout = self.property_set[to_layout] - except Exception: + + if to_layout is None: + to_layout = Layout.generate_trivial_layout(*dag.qregs.values()) + elif isinstance(to_layout, str): + to_layout = self.property_set[to_layout] + if to_layout is None: raise TranspilerError('No {} (to_layout) in property_set.'.format(to_layout)) + else: + raise TranspilerError('to_layout parameter should be a Layout, a string, or None.') # Find the permutation between the initial physical qubits and final physical qubits. - permutation = {pqubit: to_layout.get_virtual_bits()[vqubit] - for vqubit, pqubit in from_layout.get_virtual_bits().items()} + permutation = {pqubit: self.property_set['layout'][vqubit] for vqubit, pqubit in + from_layout.get_virtual_bits().items()} perm_circ = self.token_swapper.permutation_circuit(permutation, self.trials) qubits = [dag.qubits[i[0]] for i in sorted(perm_circ.inputmap.items(), key=lambda x: x[0])] dag.compose(perm_circ.circuit, qubits=qubits) + + # Reset the layout to trivial + self.property_set["layout"] = Layout.generate_trivial_layout(*dag.qregs.values()) + return dag diff --git a/qiskit/transpiler/passmanager_config.py b/qiskit/transpiler/passmanager_config.py index b1f635cf8460..c8c2cf9ece10 100644 --- a/qiskit/transpiler/passmanager_config.py +++ b/qiskit/transpiler/passmanager_config.py @@ -27,7 +27,8 @@ def __init__(self, scheduling_method=None, instruction_durations=None, backend_properties=None, - seed_transpiler=None): + seed_transpiler=None, + restore_layout=None): """Initialize a PassManagerConfig object Args: @@ -50,6 +51,7 @@ def __init__(self, qubit coherence times, etc. seed_transpiler (int): Sets random seed for the stochastic parts of the transpiler. + restore_layout (str): `trivial`, `initial`, or None """ self.initial_layout = initial_layout self.basis_gates = basis_gates @@ -61,3 +63,4 @@ def __init__(self, self.instruction_durations = instruction_durations self.backend_properties = backend_properties self.seed_transpiler = seed_transpiler + self.restore_layout = restore_layout diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index bc59d3c7cbd7..4fb3d36fe04f 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -35,13 +35,14 @@ from qiskit.transpiler.passes import LookaheadSwap from qiskit.transpiler.passes import StochasticSwap from qiskit.transpiler.passes import SabreSwap +from qiskit.transpiler.passes import LayoutTransformation from qiskit.transpiler.passes import FullAncillaAllocation from qiskit.transpiler.passes import EnlargeWithAncilla from qiskit.transpiler.passes import FixedPoint from qiskit.transpiler.passes import Depth from qiskit.transpiler.passes import RemoveResetInZeroState from qiskit.transpiler.passes import Optimize1qGatesDecomposition -from qiskit.transpiler.passes import ApplyLayout +from qiskit.transpiler.passes import ApplyLayoutSwaps from qiskit.transpiler.passes import CheckCXDirection from qiskit.transpiler.passes import Layout2qDistance from qiskit.transpiler.passes import Collect2qBlocks @@ -90,6 +91,7 @@ def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: instruction_durations = pass_manager_config.instruction_durations seed_transpiler = pass_manager_config.seed_transpiler backend_properties = pass_manager_config.backend_properties + restore_layout = pass_manager_config.restore_layout # 1. Use trivial layout if no layout given _given_layout = SetLayout(initial_layout) @@ -118,7 +120,7 @@ def _not_perfect_yet(property_set): property_set['trivial_layout_score'] != 0 # 3. Extend dag/layout with ancillas using the full coupling map - _embed = [FullAncillaAllocation(coupling_map), EnlargeWithAncilla(), ApplyLayout()] + _embed = [FullAncillaAllocation(coupling_map), EnlargeWithAncilla()] # 4. Decompose so only 1-qubit and 2-qubit gates remain _unroll3q = Unroll3qOrMore() @@ -144,6 +146,9 @@ def _swap_condition(property_set): else: raise TranspilerError("Invalid routing method %s." % routing_method) + _restore_layout = [LayoutTransformation(coupling_map, 'layout_out', None, + seed=seed_transpiler, trials=4)] + # 6. Unroll to the basis if translation_method == 'unroller': _unroll = [Unroller(basis_gates)] @@ -197,9 +202,11 @@ def _opt_control(property_set): pm1.append(_choose_layout_and_score, condition=_choose_layout_condition) pm1.append(_improve_layout, condition=_not_perfect_yet) pm1.append(_embed) + pm1.append(ApplyLayoutSwaps(coupling_map, seed=seed_transpiler, trials=4)) pm1.append(_unroll3q) pm1.append(_swap_check) pm1.append(_swap, condition=_swap_condition) + pm1.append(_restore_layout) pm1.append(_unroll) if coupling_map and not coupling_map.is_symmetric: pm1.append(_direction_check)