diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index dc93c85a5fcf..706a223f8c92 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -28,7 +28,6 @@ from qiskit import user_config from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.circuit.quantumregister import Qubit -from qiskit.converters import isinstanceint, isinstancelist from qiskit.dagcircuit import DAGCircuit from qiskit.providers.backend import Backend from qiskit.providers.models import BackendProperties @@ -757,16 +756,27 @@ def _parse_initial_layout(initial_layout, circuits): def _layout_from_raw(initial_layout, circuit): if initial_layout is None or isinstance(initial_layout, Layout): return initial_layout - elif isinstancelist(initial_layout): - if all(isinstanceint(elem) for elem in initial_layout): - initial_layout = Layout.from_intlist(initial_layout, *circuit.qregs) - elif all(elem is None or isinstance(elem, Qubit) for elem in initial_layout): - initial_layout = Layout.from_qubit_list(initial_layout, *circuit.qregs) - elif isinstance(initial_layout, dict): - initial_layout = Layout(initial_layout) + if isinstance(initial_layout, dict): + return Layout(initial_layout) + # Should be an iterable either of ints or bits/None. + specifier = tuple(initial_layout) + if all(phys is None or isinstance(phys, Qubit) for phys in specifier): + mapping = {phys: virt for phys, virt in enumerate(specifier) if virt is not None} + if len(mapping) != circuit.num_qubits: + raise TranspilerError( + f"'initial_layout' ({len(mapping)}) and circuit ({circuit.num_qubits}) had" + " different numbers of qubits" + ) else: - raise TranspilerError("The initial_layout parameter could not be parsed") - return initial_layout + if len(specifier) != circuit.num_qubits: + raise TranspilerError( + f"'initial_layout' ({len(specifier)}) and circuit ({circuit.num_qubits}) had" + " different numbers of qubits" + ) + if len(specifier) != len(set(specifier)): + raise TranspilerError(f"'initial_layout' contained duplicate entries: {specifier}") + mapping = {int(phys): virt for phys, virt in zip(specifier, circuit.qubits)} + return Layout(mapping) # multiple layouts? if isinstance(initial_layout, list) and any( diff --git a/releasenotes/notes/fix-initial_layout-loose-qubits-0c59b2d6fb99d7e6.yaml b/releasenotes/notes/fix-initial_layout-loose-qubits-0c59b2d6fb99d7e6.yaml new file mode 100644 index 000000000000..4bac5214f5c6 --- /dev/null +++ b/releasenotes/notes/fix-initial_layout-loose-qubits-0c59b2d6fb99d7e6.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Using ``initial_layout`` in calls to :func:`.transpile` will no longer error if the + circuit contains qubits not in any registers, or qubits that exist in more than one + register. See `#10125 `__. diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index fe5670aba861..c25425d051f1 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -627,14 +627,9 @@ def test_wrong_initial_layout(self): QuantumRegister(3, "q")[2], ] - with self.assertRaises(TranspilerError) as cm: + with self.assertRaisesRegex(TranspilerError, "different numbers of qubits"): transpile(qc, backend, initial_layout=bad_initial_layout) - self.assertEqual( - "FullAncillaAllocation: The layout refers to a qubit that does not exist in circuit.", - cm.exception.message, - ) - def test_parameterized_circuit_for_simulator(self): """Verify that a parameterized circuit can be transpiled for a simulator backend.""" qr = QuantumRegister(2, name="qr") @@ -1616,6 +1611,30 @@ def test_transpile_identity_circuit_no_target(self, opt_level): result = transpile(qc, optimization_level=opt_level) self.assertEqual(empty_qc, result) + @data(0, 1, 2, 3) + def test_initial_layout_with_loose_qubits(self, opt_level): + """Regression test of gh-10125.""" + qc = QuantumCircuit([Qubit(), Qubit()]) + qc.cx(0, 1) + transpiled = transpile(qc, initial_layout=[1, 0], optimization_level=opt_level) + self.assertIsNotNone(transpiled.layout) + self.assertEqual( + transpiled.layout.initial_layout, Layout({0: qc.qubits[1], 1: qc.qubits[0]}) + ) + + @data(0, 1, 2, 3) + def test_initial_layout_with_overlapping_qubits(self, opt_level): + """Regression test of gh-10125.""" + qr1 = QuantumRegister(2, "qr1") + qr2 = QuantumRegister(bits=qr1[:]) + qc = QuantumCircuit(qr1, qr2) + qc.cx(0, 1) + transpiled = transpile(qc, initial_layout=[1, 0], optimization_level=opt_level) + self.assertIsNotNone(transpiled.layout) + self.assertEqual( + transpiled.layout.initial_layout, Layout({0: qc.qubits[1], 1: qc.qubits[0]}) + ) + @ddt class TestPostTranspileIntegration(QiskitTestCase):