diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py index 6125b884061a..e0ae22a7ec1e 100644 --- a/qiskit/transpiler/passes/__init__.py +++ b/qiskit/transpiler/passes/__init__.py @@ -48,6 +48,7 @@ from .mapping.dense_layout import DenseLayout from .mapping.noise_adaptive_layout import NoiseAdaptiveLayout from .mapping.basic_swap import BasicSwap +from .mapping.layout_2q_distance import Layout2qDistance from .mapping.lookahead_swap import LookaheadSwap from .remove_diagonal_gates_before_measure import RemoveDiagonalGatesBeforeMeasure from .mapping.stochastic_swap import StochasticSwap diff --git a/qiskit/transpiler/passes/mapping/layout_2q_distance.py b/qiskit/transpiler/passes/mapping/layout_2q_distance.py new file mode 100644 index 000000000000..36eb2aa45763 --- /dev/null +++ b/qiskit/transpiler/passes/mapping/layout_2q_distance.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019. +# +# 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. + +"""Evaluate how good the layout selection was. + +No CX direction is considered. +Saves in `property_set['layout_score']` the sum of distances for each circuit CX. +The lower the number, the better the selection. +Therefore, 0 is a perfect layout selection. +""" + +from qiskit.transpiler.basepasses import AnalysisPass + + +class Layout2qDistance(AnalysisPass): + """Evaluate how good the layout selection was. + + Saves in `property_set['layout_score']` (or the property name in property_name) + the sum of distances for each circuit CX. + The lower the number, the better the selection. Therefore, 0 is a perfect layout selection. + No CX direction is considered. + """ + def __init__(self, coupling_map, property_name='layout_score'): + """Layout2qDistance initializer. + + Args: + coupling_map (CouplingMap): Directed graph represented a coupling map. + property_name (str): The property name to save the score. Default: layout_score + """ + super().__init__() + self.coupling_map = coupling_map + self.property_name = property_name + + def run(self, dag): + """ + Run the Layout2qDistance pass on `dag`. + Args: + dag (DAGCircuit): DAG to evaluate. + """ + layout = self.property_set["layout"] + + if layout is None: + return + + sum_distance = 0 + + for gate in dag.twoQ_gates(): + physical_q0 = layout[gate.qargs[0]] + physical_q1 = layout[gate.qargs[1]] + + sum_distance += self.coupling_map.distance(physical_q0, physical_q1)-1 + + self.property_set[self.property_name] = sum_distance diff --git a/releasenotes/notes/Layout2qDistance-84fccae5eb89699c.yaml b/releasenotes/notes/Layout2qDistance-84fccae5eb89699c.yaml new file mode 100644 index 000000000000..a3697bd7e002 --- /dev/null +++ b/releasenotes/notes/Layout2qDistance-84fccae5eb89699c.yaml @@ -0,0 +1,21 @@ +--- +features: + - | + A new analysis pass is added: ``Layout2qDistance``. This pass allows to "score" a + layout selection, once ``property_set['layout']`` is set. + The score will be the sum of distances for each two-qubit gate in the circuit, + when they are not directly connected. + This scoring does not consider direction in the coupling map. + The lower the number, the better the layout selection is. + + For example, consider a linear coupling map ``[0]--[2]--[1]`` and the + following circuit: + + qr = QuantumRegister(2, 'qr') + circuit = QuantumCircuit(qr) + circuit.cx(qr[0], qr[1]) + + If the layout is ``{qr[0]:0, qr[1]:1}``, ``Layout2qDistance`` will set + ``property_set['layout_score'] = 1``. If the layout + is ``{qr[0]:0, qr[1]:2}``, then the result + is ``property_set['layout_score'] = 0``. The lower the score, the better. \ No newline at end of file diff --git a/test/python/transpiler/test_layout_score.py b/test/python/transpiler/test_layout_score.py new file mode 100644 index 000000000000..a9f524c55948 --- /dev/null +++ b/test/python/transpiler/test_layout_score.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019. +# +# 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. + +"""Test the Layout Score pass""" + +import unittest + +from qiskit import QuantumRegister, QuantumCircuit +from qiskit.transpiler.passes import Layout2qDistance +from qiskit.transpiler import CouplingMap, Layout +from qiskit.converters import circuit_to_dag +from qiskit.test import QiskitTestCase + + +class TestLayoutScoreError(QiskitTestCase): + """Test error-ish of Layout Score""" + + def test_no_layout(self): + """No Layout. Empty Circuit CouplingMap map: None. Result: None + """ + qr = QuantumRegister(3, 'qr') + circuit = QuantumCircuit(qr) + coupling = CouplingMap() + layout = None + + dag = circuit_to_dag(circuit) + pass_ = Layout2qDistance(coupling) + pass_.property_set['layout'] = layout + pass_.run(dag) + + self.assertIsNone(pass_.property_set['layout_score']) + + +class TestTrivialLayoutScore(QiskitTestCase): + """ Trivial layout scenarios""" + + def test_no_cx(self): + """Empty Circuit CouplingMap map: None. Result: 0 + """ + qr = QuantumRegister(3, 'qr') + circuit = QuantumCircuit(qr) + coupling = CouplingMap() + layout = Layout().generate_trivial_layout(qr) + + dag = circuit_to_dag(circuit) + pass_ = Layout2qDistance(coupling) + pass_.property_set['layout'] = layout + pass_.run(dag) + + self.assertEqual(pass_.property_set['layout_score'], 0) + + def test_swap_mapped_true(self): + """ Mapped circuit. Good Layout + qr0 (0):--(+)---(+)- + | | + qr1 (1):---.-----|-- + | + qr2 (2):---------.-- + + CouplingMap map: [1]--[0]--[2] + """ + qr = QuantumRegister(3, 'qr') + circuit = QuantumCircuit(qr) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[0], qr[2]) + coupling = CouplingMap([[0, 1], [0, 2]]) + layout = Layout().generate_trivial_layout(qr) + + dag = circuit_to_dag(circuit) + pass_ = Layout2qDistance(coupling) + pass_.property_set['layout'] = layout + pass_.run(dag) + + self.assertEqual(pass_.property_set['layout_score'], 0) + + def test_swap_mapped_false(self): + """ Needs [0]-[1] in a [0]--[2]--[1] Result:1 + qr0:--(+)-- + | + qr1:---.--- + + CouplingMap map: [0]--[2]--[1] + """ + qr = QuantumRegister(2, 'qr') + circuit = QuantumCircuit(qr) + circuit.cx(qr[0], qr[1]) + coupling = CouplingMap([[0, 2], [2, 1]]) + layout = Layout().generate_trivial_layout(qr) + + dag = circuit_to_dag(circuit) + pass_ = Layout2qDistance(coupling) + pass_.property_set['layout'] = layout + pass_.run(dag) + + self.assertEqual(pass_.property_set['layout_score'], 1) + + +if __name__ == '__main__': + unittest.main()