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

Undoing swappers with LayoutTransformation #5280

Closed
wants to merge 24 commits into from
Closed
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
2 changes: 2 additions & 0 deletions qiskit/transpiler/passes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
SabreLayout
CSPLayout
ApplyLayout
ApplyLayoutSwap
Layout2qDistance
EnlargeWithAncilla
FullAncillaAllocation
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions qiskit/transpiler/passes/layout/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion qiskit/transpiler/passes/layout/apply_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
Expand Down
89 changes: 89 additions & 0 deletions qiskit/transpiler/passes/layout/apply_layout_swap.py
Original file line number Diff line number Diff line change
@@ -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
4 changes: 2 additions & 2 deletions qiskit/transpiler/passes/routing/basic_swap.py
Original file line number Diff line number Diff line change
Expand Up @@ -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']
Expand Down Expand Up @@ -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
35 changes: 22 additions & 13 deletions qiskit/transpiler/passes/routing/layout_transformation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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.
Expand All @@ -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

Expand All @@ -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
5 changes: 4 additions & 1 deletion qiskit/transpiler/passmanager_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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
11 changes: 9 additions & 2 deletions qiskit/transpiler/preset_passmanagers/level1.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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()
Expand All @@ -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)]
Expand Down Expand Up @@ -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)
Expand Down