Fix `initial_layout` in `transpile` with loose qubits (#10153)

The previous logic around building a `Layout` object from an
`initial_layout` was still couched in the "registers own bits" model, so
could not cope with loose bits.  It's most convenient to just build the
mapping ourselves during the parsing, since the logic is quite specific
to the form of the `initial_layout` argument, rather than being
something that's inherently tied to the `Layout` class.
This commit is contained in:
Jake Lishman 2023-05-24 20:31:30 +01:00 committed by GitHub
parent 558fb0ad21
commit 716b648cb9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 51 additions and 16 deletions

View File

@ -28,7 +28,6 @@ import warnings
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(

View File

@ -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 <https://github.com/Qiskit/qiskit-terra/issues/10125>`__.

View File

@ -627,14 +627,9 @@ class TestTranspile(QiskitTestCase):
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 @@ class TestTranspile(QiskitTestCase):
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):