Skip to content

Commit

Permalink
Fix initial_layout in transpile with loose qubits (#10153)
Browse files Browse the repository at this point in the history
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.

(cherry picked from commit 716b648)
  • Loading branch information
jakelishman authored and mergify[bot] committed May 24, 2023
1 parent 31fb630 commit f8ed13c
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 16 deletions.
30 changes: 20 additions & 10 deletions qiskit/compiler/transpiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,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
Expand Down Expand Up @@ -762,16 +761,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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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>`__.
31 changes: 25 additions & 6 deletions test/python/compiler/test_transpiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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):
Expand Down

0 comments on commit f8ed13c

Please sign in to comment.