diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index c9453c017d6e..9b777e79e684 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -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 @@ -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': @@ -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) diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index c76a441ff3db..a749f792e24c 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -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 @@ -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': @@ -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) diff --git a/test/python/transpiler/test_preset_passmanagers.py b/test/python/transpiler/test_preset_passmanagers.py index 86cabbb477f2..2adac533354f 100644 --- a/test/python/transpiler/test_preset_passmanagers.py +++ b/test/python/transpiler/test_preset_passmanagers.py @@ -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(): @@ -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