Skip to content

Commit

Permalink
Always try initial layout as first guess in preset passmanagers (#5702)
Browse files Browse the repository at this point in the history
* Always try initial layout as first guess in preset passmanagers

In the preset passmanagers level2 and level3 the initial layout is
generated by the CSP layout pass and then if an answer can't be found
the specified layout method is used instead. However, as was reported in
issue #5694 the CSP layout completely misses when the trivial layout case
perfectly maps the input circuit to the device. This results in level 1
significantly out performing level2 and level3 because it has to go to
routing and inserts a lot of swaps. To address this hole in the CSP
layout case this commit updates the preset passmanagers for level 2 and
level 3 to always try a trivial layout first, if this doesn't result in
a perfect mapping the pass is unchanged it will use CSP layout and then
the configured layout method.

Fixes #5694

* Tweak flow to not use trivial unless perfect

* Fix lint

* Add comments and explain flow

Co-authored-by: Luciano Bello <bel@zurich.ibm.com>
  • Loading branch information
mtreinish and 1ucian0 authored Apr 14, 2021
1 parent 40ffd2b commit caa3131
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 4 deletions.
37 changes: 35 additions & 2 deletions qiskit/transpiler/preset_passmanagers/level2.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
from qiskit.transpiler.passes import Optimize1qGatesDecomposition
from qiskit.transpiler.passes import CommutativeCancellation
from qiskit.transpiler.passes import ApplyLayout
from qiskit.transpiler.passes import Layout2qDistance
from qiskit.transpiler.passes import CheckGateDirection
from qiskit.transpiler.passes import Collect2qBlocks
from qiskit.transpiler.passes import ConsolidateBlocks
Expand Down Expand Up @@ -99,10 +100,41 @@ def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
_given_layout = SetLayout(initial_layout)

def _choose_layout_condition(property_set):
# layout hasn't been set yet
return not property_set['layout']

# 1a. If layout_method is not set, first try a trivial layout
_choose_layout_0 = [] if pass_manager_config.layout_method \
else [TrivialLayout(coupling_map),
Layout2qDistance(coupling_map,
property_name='trivial_layout_score')]
# 1b. If a trivial layout wasn't perfect (ie no swaps are needed) then try using
# CSP layout to find a perfect layout
_choose_layout_1 = [] if pass_manager_config.layout_method \
else CSPLayout(coupling_map, call_limit=1000, time_limit=10, seed=seed_transpiler)

def _trivial_not_perfect(property_set):
# Verify that a trivial layout is perfect. If trivial_layout_score > 0
# the layout is not perfect. The layout is unconditionally set by trivial
# layout so we need to clear it before contuing.
if property_set['trivial_layout_score'] is not None:
if property_set['trivial_layout_score'] != 0:
property_set['layout']._wrapped = None
return True
return False

def _csp_not_found_match(property_set):
# If a layout hasn't been set by the time we run csp we need to run layout
if property_set['layout'] is None:
return True
# if CSP layout stopped for any reason other than solution found we need
# to run layout since CSP didn't converge.
if property_set['CSPLayout_stop_reason'] is not None \
and property_set['CSPLayout_stop_reason'] != "solution found":
return True
return False

# 1c. if CSP layout doesn't converge on a solution use layout_method (dense) to get a layout
if layout_method == 'trivial':
_choose_layout_2 = TrivialLayout(coupling_map)
elif layout_method == 'dense':
Expand Down Expand Up @@ -193,8 +225,9 @@ def _opt_control(property_set):
pm2 = PassManager()
if coupling_map or initial_layout:
pm2.append(_given_layout)
pm2.append(_choose_layout_1, condition=_choose_layout_condition)
pm2.append(_choose_layout_2, condition=_choose_layout_condition)
pm2.append(_choose_layout_0, condition=_choose_layout_condition)
pm2.append(_choose_layout_1, condition=_trivial_not_perfect)
pm2.append(_choose_layout_2, condition=_csp_not_found_match)
pm2.append(_embed)
pm2.append(_unroll3q)
pm2.append(_swap_check)
Expand Down
37 changes: 35 additions & 2 deletions qiskit/transpiler/preset_passmanagers/level3.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
from qiskit.transpiler.passes import ConsolidateBlocks
from qiskit.transpiler.passes import UnitarySynthesis
from qiskit.transpiler.passes import ApplyLayout
from qiskit.transpiler.passes import Layout2qDistance
from qiskit.transpiler.passes import CheckGateDirection
from qiskit.transpiler.passes import TimeUnitConversion
from qiskit.transpiler.passes import ALAPSchedule
Expand Down Expand Up @@ -105,10 +106,41 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
_given_layout = SetLayout(initial_layout)

def _choose_layout_condition(property_set):
# layout hasn't been set yet
return not property_set['layout']

def _csp_not_found_match(property_set):
# If a layout hasn't been set by the time we run csp we need to run layout
if property_set['layout'] is None:
return True
# if CSP layout stopped for any reason other than solution found we need
# to run layout since CSP didn't converge.
if property_set['CSPLayout_stop_reason'] is not None \
and property_set['CSPLayout_stop_reason'] != "solution found":
return True
return False

# 2a. If layout method is not set, first try a trivial layout
_choose_layout_0 = [] if pass_manager_config.layout_method \
else [TrivialLayout(coupling_map),
Layout2qDistance(coupling_map,
property_name='trivial_layout_score')]
# 2b. If trivial layout wasn't perfect (ie no swaps are needed) then try
# using CSP layout to find a perfect layout
_choose_layout_1 = [] if pass_manager_config.layout_method \
else CSPLayout(coupling_map, call_limit=10000, time_limit=60, seed=seed_transpiler)

def _trivial_not_perfect(property_set):
# Verify that a trivial layout is perfect. If trivial_layout_score > 0
# the layout is not perfect. The layout property set is unconditionally
# set by trivial layout so we clear that before running CSP
if property_set['trivial_layout_score'] is not None:
if property_set['trivial_layout_score'] != 0:
property_set['layout']._wrapped = None
return True
return False

# 2c. if CSP didn't converge on a solution use layout_method (dense).
if layout_method == 'trivial':
_choose_layout_2 = TrivialLayout(coupling_map)
elif layout_method == 'dense':
Expand Down Expand Up @@ -205,8 +237,9 @@ def _opt_control(property_set):
pm3.append(_reset + _meas)
if coupling_map or initial_layout:
pm3.append(_given_layout)
pm3.append(_choose_layout_1, condition=_choose_layout_condition)
pm3.append(_choose_layout_2, condition=_choose_layout_condition)
pm3.append(_choose_layout_0, condition=_choose_layout_condition)
pm3.append(_choose_layout_1, condition=_trivial_not_perfect)
pm3.append(_choose_layout_2, condition=_csp_not_found_match)
pm3.append(_embed)
pm3.append(_swap_check)
pm3.append(_swap, condition=_swap_condition)
Expand Down
43 changes: 43 additions & 0 deletions test/python/transpiler/test_preset_passmanagers.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from qiskit.test.mock import (FakeTenerife, FakeMelbourne, FakeJohannesburg,
FakeRueschlikon, FakeTokyo, FakePoughkeepsie)
from qiskit.converters import circuit_to_dag
from qiskit.circuit.library import GraphState


def emptycircuit():
Expand Down Expand Up @@ -482,6 +483,48 @@ def test_layout_tokyo_fully_connected_cx(self, level):
result = transpile(qc, backend, optimization_level=level, seed_transpiler=42)
self.assertEqual(result._layout._p2v, expected_layouts[level])

@data(0, 1, 2, 3)
def test_all_levels_use_trivial_if_perfect(self, level):
"""Test that we always use trivial if it's a perfect match.
See: https://github.com/Qiskit/qiskit-terra/issues/5694 for more
details
"""
backend = FakeTokyo()
config = backend.configuration()

rows = [x[0] for x in config.coupling_map]
cols = [x[1] for x in config.coupling_map]

adjacency_matrix = np.zeros((20, 20))
adjacency_matrix[rows, cols] = 1
qc = GraphState(adjacency_matrix)
qc.measure_all()
expected = {
0: Qubit(QuantumRegister(20, 'q'), 0),
1: Qubit(QuantumRegister(20, 'q'), 1),
2: Qubit(QuantumRegister(20, 'q'), 2),
3: Qubit(QuantumRegister(20, 'q'), 3),
4: Qubit(QuantumRegister(20, 'q'), 4),
5: Qubit(QuantumRegister(20, 'q'), 5),
6: Qubit(QuantumRegister(20, 'q'), 6),
7: Qubit(QuantumRegister(20, 'q'), 7),
8: Qubit(QuantumRegister(20, 'q'), 8),
9: Qubit(QuantumRegister(20, 'q'), 9),
10: Qubit(QuantumRegister(20, 'q'), 10),
11: Qubit(QuantumRegister(20, 'q'), 11),
12: Qubit(QuantumRegister(20, 'q'), 12),
13: Qubit(QuantumRegister(20, 'q'), 13),
14: Qubit(QuantumRegister(20, 'q'), 14),
15: Qubit(QuantumRegister(20, 'q'), 15),
16: Qubit(QuantumRegister(20, 'q'), 16),
17: Qubit(QuantumRegister(20, 'q'), 17),
18: Qubit(QuantumRegister(20, 'q'), 18),
19: Qubit(QuantumRegister(20, 'q'), 19)
}
trans_qc = transpile(qc, backend, optimization_level=level)
self.assertEqual(trans_qc._layout._p2v, expected)

@data(0, 1)
def test_trivial_layout(self, level):
"""Test that trivial layout is preferred in level 0 and 1
Expand Down

0 comments on commit caa3131

Please sign in to comment.