Source code for qrisp.circuit.pass_management.passes.manual_layout
# This work is licensed under the European Union Public Licence (EUPL), Version 1.2.
#
# You may not use this work except in compliance with the Licence.
# You may obtain a copy of the Licence at:
#
# https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
#
# Unless required by applicable law or agreed to in writing, software distributed under
# the Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the Licence for the specific language
# governing permissions and limitations under the Licence.
"""Manual qubit layout pass.
Creates a pass function that re-indexes qubits according to a caller-supplied
physical qubit mapping. Logical qubit *i* is placed on physical qubit
``qubit_mapping[i]``. The output circuit may have more qubits than the
input if the maximum physical index exceeds the logical qubit count.
"""
from __future__ import annotations
from collections.abc import Callable
from qrisp.circuit.pass_management.circuit_pass import CircuitPass
from qrisp.circuit.quantum_circuit import QuantumCircuit
from qrisp.circuit.qubit import Qubit
[docs]
def manual_layout(
qubit_mapping: list[int],
) -> Callable[[QuantumCircuit], QuantumCircuit]:
"""Create a pass that applies a manual qubit layout to the circuit.
Parameters
----------
qubit_mapping : list[int]
A list of physical qubit indices. The *i*-th logical qubit in the
circuit is mapped to physical qubit ``qubit_mapping[i]``.
For example, ``[2, 0, 1]`` means:
* Logical qubit 0 → Physical qubit 2
* Logical qubit 1 → Physical qubit 0
* Logical qubit 2 → Physical qubit 1
Returns
-------
Callable[[QuantumCircuit], QuantumCircuit]
A pass function that transforms the circuit.
Raises
------
ValueError
If the mapping length does not match the circuit qubit count, if any
index is negative, or if there are duplicate indices.
Examples
--------
>>> from qrisp import QuantumCircuit, PassManager
>>> from qrisp import manual_layout
>>> qc = QuantumCircuit(3)
>>> qc.h(0)
>>> qc.cx(1, 2)
>>> print(qc)
┌───┐
qb_0: ┤ H ├
└───┘
qb_1: ──■──
┌─┴─┐
qb_2: ┤ X ├
└───┘
>>> pm = PassManager()
>>> pm += manual_layout([2, 0, 1]) # Logical 0→2, 1→0, 2→1
>>> new_layout_qc = pm.run(qc)
>>> print(new_layout_qc)
<BLANKLINE>
qb_1: ──■──
┌─┴─┐
qb_2: ┤ X ├
├───┤
qb_0: ┤ H ├
└───┘
"""
@CircuitPass
def _manual_layout(qc: QuantumCircuit) -> QuantumCircuit:
num_circuit_qubits = qc.num_qubits()
if len(qubit_mapping) != num_circuit_qubits:
raise ValueError(
f"qubit_mapping specifies {len(qubit_mapping)} qubits, but the circuit has {num_circuit_qubits} qubits."
)
for idx in qubit_mapping:
if idx < 0:
raise ValueError(f"Qubit index {idx} is invalid. Indices must be non-negative.")
if len(qubit_mapping) != len(set(qubit_mapping)):
raise ValueError(
f"Duplicate qubit indices in qubit_mapping: {qubit_mapping}. "
f"Each circuit qubit must be mapped to a unique physical qubit."
)
num_physical_qubits = max(qubit_mapping) + 1 if qubit_mapping else 0
new_qc = qc.copy()
amended_qubits: list[Qubit] = []
while new_qc.num_qubits() < num_physical_qubits:
amended_qubits.append(Qubit("amended_qb_" + str(len(amended_qubits))))
new_qc.add_qubit(amended_qubits[-1])
reverse_mapping = {phys: log for log, phys in enumerate(qubit_mapping)}
new_qubit_list: list[Qubit] = []
for i in range(num_physical_qubits):
if i in qubit_mapping:
new_qubit_list.append(qc.qubits[reverse_mapping[i]])
else:
new_qubit_list.append(amended_qubits.pop(0))
new_qc.qubits = new_qubit_list
return new_qc
_manual_layout.__name__ = "manual_layout"
return _manual_layout