Source code for qrisp.circuit.pass_management.passes.arrange_swaps

"""********************************************************************************
* Copyright (c) 2026 the Qrisp authors
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License, v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is
* available at https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************
"""

from __future__ import annotations

from qrisp.circuit.pass_management.circuit_pass import CircuitPass
from qrisp.circuit.quantum_circuit import QuantumCircuit


[docs] @CircuitPass def arrange_swaps(qc: QuantumCircuit) -> QuantumCircuit: r"""Flip SWAP qubit order so that unused qubits come first. A SWAP gate decomposes into three CX gates. When a SWAP involves a qubit that has not yet been touched by any prior operation, that qubit is in the \|0⟩ state, and the first CX in the decomposition is controlled on \|0⟩ — a no-op. This pass reorders the qubits of each SWAP so that any untouched qubit appears first. This exposes the redundant CX to downstream passes such as :func:`~qrisp.cancel_zero_controls`, which can then remove it. After reordering, SWAPs that involve **two** untouched qubits are dropped entirely (a SWAP between \|0⟩ states does nothing). SWAPs where both qubits have already been used are left in their original order. Parameters ---------- qc : QuantumCircuit Input quantum circuit. Returns ------- QuantumCircuit Circuit with SWAP qubit order flipped where beneficial. Examples -------- A SWAP(0, 1) where qubit 1 hasn't been touched yet. Setup:: >>> from qrisp import QuantumCircuit, PassManager, decompose >>> from qrisp import arrange_swaps, cancel_zero_controls >>> >>> qc = QuantumCircuit(2) >>> qc.x(0) >>> qc.swap(0, 1) # qubit 1 is untouched Without ``arrange_swaps``, the SWAP decomposes into three CX gates. The first CX targets the unused qubit (qubit 1), but since that qubit is still in \|0⟩ the CX is a no-op — it remains in the circuit anyway:: >>> pm_raw = PassManager() >>> pm_raw += decompose() >>> print(pm_raw.run(qc)) # doctest: +SKIP ┌───┐ ┌───┐ qb_0: ┤ X ├──■──┤ X ├──■── └───┘┌─┴─┐└─┬─┘┌─┴─┐ qb_1: ─────┤ X ├──■──┤ X ├ └───┘ └───┘ With ``arrange_swaps`` the qubit order is flipped so the unused qubit comes first. ``cancel_zero_controls`` then removes the first CX (controlled on \|0⟩). After decomposition, one CX is gone:: >>> pm = PassManager() >>> pm += arrange_swaps >>> pm += decompose() >>> pm += cancel_zero_controls >>> print(pm.run(qc)) ┌───┐ ┌───┐ qb_0: ┤ X ├──■──┤ X ├ └───┘┌─┴─┐└─┬─┘ qb_1: ─────┤ X ├──■── └───┘ A SWAP between **two** untouched qubits is dropped entirely (a SWAP between \|0⟩ states is the identity): >>> qc = QuantumCircuit(2) >>> qc.swap(0, 1) >>> result = pm.run(qc) >>> print(pm.run(qc)) <BLANKLINE> qb_0: <BLANKLINE> qb_1: """ qc_new = qc.clearcopy() used_qubits = set() for instr in qc.data: # Skip allocation instructions if "alloc" in instr.op.name: continue if instr.op.name == "swap": # Check if second qubit is unused if instr.qubits[1] not in used_qubits: # If both qubits are unused, skip this SWAP entirely if instr.qubits[0] not in used_qubits: continue # If first qubit is used but second is unused, reverse order # This puts the unused qubit first for optimal CX decomposition instr = instr.copy() instr.qubits = instr.qubits[::-1] # Track which qubits have been used used_qubits.update(instr.qubits) qc_new.append(instr) return qc_new