diff --git a/qiskit/transpiler/layout.py b/qiskit/transpiler/layout.py index c2f064bcb360..5f20404bbc97 100644 --- a/qiskit/transpiler/layout.py +++ b/qiskit/transpiler/layout.py @@ -129,6 +129,11 @@ def __delitem__(self, key): def __len__(self): return len(self._p2v) + def __eq__(self, other): + if isinstance(other, Layout): + return self._p2v == other._p2v and self._v2p == other._v2p + return False + def copy(self): """Returns a copy of a Layout instance.""" layout_copy = type(self)() diff --git a/qiskit/transpiler/passes/layout/full_ancilla_allocation.py b/qiskit/transpiler/passes/layout/full_ancilla_allocation.py index 1302e4ab06bf..db365a676be1 100644 --- a/qiskit/transpiler/passes/layout/full_ancilla_allocation.py +++ b/qiskit/transpiler/passes/layout/full_ancilla_allocation.py @@ -65,10 +65,17 @@ def run(self, dag): if layout is None: raise TranspilerError('FullAncillaAllocation pass requires property_set["layout"].') - layout_physical_qubits = layout.get_physical_bits().keys() - coupling_physical_qubits = self.coupling_map.physical_qubits - idle_physical_qubits = [q for q in coupling_physical_qubits - if q not in layout_physical_qubits] + if layout: + layout_physical_qubits = list(range(max(layout.get_physical_bits()) + 1)) + else: + layout_physical_qubits = [] + + idle_physical_qubits = [q for q in layout_physical_qubits + if q not in layout.get_physical_bits()] + + if self.coupling_map: + idle_physical_qubits = [q for q in self.coupling_map.physical_qubits + if q not in layout.get_physical_bits()] if idle_physical_qubits: if self.ancilla_name in dag.qregs: diff --git a/qiskit/transpiler/passes/utils/check_map.py b/qiskit/transpiler/passes/utils/check_map.py index ba8add50c0bd..56f52637304c 100644 --- a/qiskit/transpiler/passes/utils/check_map.py +++ b/qiskit/transpiler/passes/utils/check_map.py @@ -43,6 +43,9 @@ def run(self, dag): """ self.property_set['is_swap_mapped'] = True + if self.coupling_map is None: + return + for gate in dag.two_qubit_ops(): physical_q0 = gate.qargs[0].index physical_q1 = gate.qargs[1].index diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index 88e9bfc7b9b1..0af86f1f6147 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -160,7 +160,7 @@ def _direction_condition(property_set): # Build pass manager pm0 = PassManager() - if coupling_map: + if coupling_map or initial_layout: pm0.append(_given_layout) pm0.append(_choose_layout, condition=_choose_layout_condition) pm0.append(_embed) diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index 74c6aa1e08e1..d7b812e44265 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -193,7 +193,7 @@ def _opt_control(property_set): # Build pass manager pm1 = PassManager() - if coupling_map: + if coupling_map or initial_layout: pm1.append(_given_layout) pm1.append(_choose_layout_and_score, condition=_choose_layout_condition) pm1.append(_improve_layout, condition=_not_perfect_yet) diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index 30ef2427e0b3..d5cc2baee51e 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -188,7 +188,7 @@ def _opt_control(property_set): # Build pass manager pm2 = PassManager() - if coupling_map: + 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) diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index 7c62dbbb46a7..45dbdac9da3c 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -207,7 +207,7 @@ def _opt_control(property_set): pm3 = PassManager() pm3.append(_unroll3q) pm3.append(_reset + _meas) - if coupling_map: + 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) diff --git a/releasenotes/notes/initial_layout_cp_none-8753d058743eeb79.yaml b/releasenotes/notes/initial_layout_cp_none-8753d058743eeb79.yaml new file mode 100644 index 000000000000..e375bf347641 --- /dev/null +++ b/releasenotes/notes/initial_layout_cp_none-8753d058743eeb79.yaml @@ -0,0 +1,48 @@ +--- +fixes: + - | + When the argument `coupling_map=None` (explicitly, implicitly as default value, or via `backend`), + the transpiling process was not "embedding" the circuit. + That is, even when an `initial_layout` was specified, the virtual qubits were not assigned to physical + qubits. Now, the function :func:`qiskit.compiler.transpile` honors the `initial_layout` argument by embedding the circuit:: + + .. code-block:: + + from qiskit import QuantumCircuit, QuantumRegister + from qiskit.compiler import transpile + + qr = QuantumRegister(2, name='qr') + circ = QuantumCircuit(qr) + circ.h(qr[0]) + circ.cx(qr[0], qr[1]) + + transpile(circ, initial_layout=[1, 0]).draw() + + + .. code-block:: + + ┌───┐ + qr_1 -> 0 ─────┤ X ├ + ┌───┐└─┬─┘ + qr_0 -> 1 ┤ H ├──■── + └───┘ + + If the `initial_layout` refers to more qubits than in the circuit, the transpiling process will extended the circuit with + ancillas. + + .. code-block:: + + transpile(circ, initial_layout=[4, 2], coupling_map=None).draw() + + .. code-block:: + + ancilla_0 -> 0 ────────── + + ancilla_1 -> 1 ────────── + ┌───┐ + qr_1 -> 2 ─────┤ X ├ + └─┬─┘ + ancilla_2 -> 3 ───────┼── + ┌───┐ │ + qr_0 -> 4 ┤ H ├──■── + └───┘ diff --git a/test/python/transpiler/test_preset_passmanagers.py b/test/python/transpiler/test_preset_passmanagers.py index 61ecf9a8d709..9dbc03121444 100644 --- a/test/python/transpiler/test_preset_passmanagers.py +++ b/test/python/transpiler/test_preset_passmanagers.py @@ -17,7 +17,7 @@ from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister from qiskit.circuit import Qubit from qiskit.compiler import transpile, assemble -from qiskit.transpiler import CouplingMap +from qiskit.transpiler import CouplingMap, Layout from qiskit.circuit.library import U2Gate, U3Gate from qiskit.test import QiskitTestCase from qiskit.test.mock import (FakeTenerife, FakeMelbourne, FakeJohannesburg, @@ -167,6 +167,42 @@ def test_symmetric_coupling_map(self, level): self.assertIn('ApplyLayout', self.passes) self.assertNotIn('CheckCXDirection', self.passes) + @data(0, 1, 2, 3) + def test_inital_layout_fully_connected_cm(self, level): + """Honor initial_layout when coupling_map=None + See: https://github.com/Qiskit/qiskit-terra/issues/5345 + """ + qr = QuantumRegister(2, 'q') + qc = QuantumCircuit(qr) + qc.h(qr[0]) + qc.cx(qr[0], qr[1]) + + transpiled = transpile(qc, initial_layout=[0, 1], optimization_level=level, + callback=self.callback) + + self.assertIn('SetLayout', self.passes) + self.assertIn('ApplyLayout', self.passes) + self.assertEqual(transpiled._layout, Layout.from_qubit_list([qr[0], qr[1]])) + + @data(0, 1, 2, 3) + def test_partial_layout_fully_connected_cm(self, level): + """Honor initial_layout (partially defined) when coupling_map=None + See: https://github.com/Qiskit/qiskit-terra/issues/5345 + """ + qr = QuantumRegister(2, 'q') + qc = QuantumCircuit(qr) + qc.h(qr[0]) + qc.cx(qr[0], qr[1]) + + transpiled = transpile(qc, initial_layout=[4, 2], optimization_level=level, + callback=self.callback) + + self.assertIn('SetLayout', self.passes) + self.assertIn('ApplyLayout', self.passes) + ancilla = QuantumRegister(3, 'ancilla') + self.assertEqual(transpiled._layout, Layout.from_qubit_list([ancilla[0], ancilla[1], qr[1], + ancilla[2], qr[0]])) + @ddt class TestInitialLayouts(QiskitTestCase):