Pass Management#
The pass management module provides a structured framework for applying sequential circuit transformations in Qrisp. It consists of two core components:
CircuitPass — A decorator/wrapper that enforces type safety on
QuantumCircuit → QuantumCircuittransformation functions and provides built-in verification capabilities (unitary comparison and measurement statistics comparison).PassManager — An ordered pipeline that chains
CircuitPassinstances and applies them sequentially. Supports pass insertion, removal, iteration, and bulk verification.
Together they allow users to compose and validate compilation workflows in a clean, reusable fashion:
Basic Pipeline#
Build a compilation pipeline by chaining passes together:
from qrisp import QuantumCircuit, PassManager
from qrisp import fuse_adjacents, commute_swaps, combine_single_qubit_gates
qc = QuantumCircuit(2)
qc.cx(0, 1)
qc.cx(0, 1) # Self-inverse — will be cancelled
qc.h(0)
qc.h(0) # Another self-inverse pair
print("Before:", qc, sep="\n")
Before:
┌───┐┌───┐
qb_0: ──■────■──┤ H ├┤ H ├
┌─┴─┐┌─┴─┐└───┘└───┘
qb_1: ┤ X ├┤ X ├──────────
└───┘└───┘
pm = PassManager()
pm += fuse_adjacents
pm += commute_swaps
pm += combine_single_qubit_gates
optimized_qc = pm.run(qc)
print("After:", optimized_qc, sep="\n")
After:
qb_0:
qb_1:
# Empty circuit — all gates cancelled
Visualizing Pass Transformations#
Every CircuitPass provides a visualize()
method that prints a before/after comparison of the circuit:
from qrisp import fuse_adjacents
qc = QuantumCircuit(2)
qc.cx(0, 1)
qc.cx(0, 1)
fuse_adjacents.visualize(qc)
==================== fuse_adjacents =====================
────────────────────────── Before ──────────────────────────
qb_0: ──■────■──
┌─┴─┐┌─┴─┐
qb_1: ┤ X ├┤ X ├
└───┘└───┘
────────────────────────── After ───────────────────────────
qb_0:
qb_1:
============================================================
This works with any pass — ideal for debugging transformations:
from qrisp import QuantumCircuit
from qrisp import convert_to_cz
qc = QuantumCircuit(2)
qc.cx(0, 1)
convert_to_cz().visualize(qc)
===================== _convert_to_cz =====================
────────────────────────── Before ──────────────────────────
qb_0: ──■──
┌─┴─┐
qb_1: ┤ X ├
└───┘
────────────────────────── After ───────────────────────────
qb_0: ──────■──────
┌───┐ │ ┌───┐
qb_1: ┤ H ├─■─┤ H ├
└───┘ └───┘
============================================================
Verification#
Verify that each pass preserves the circuit’s unitary or measurement statistics:
pm = PassManager()
pm += convert_to_cz()
pm += fuse_adjacents
pm += combine_single_qubit_gates
qc = QuantumCircuit(2)
qc.rx(0.4, 0)
qc.rz(0.2, 1)
qc.cx(0, 1)
# Check that every pass preserves the unitary
results = pm.verify(qc, "unitary", ignore_gphase=True)
for pass_name, passed in results:
print(f"{pass_name}: {'✓' if passed else '✗'}")
Standalone Passes#
Passes can also be used directly without a PassManager:
from qrisp import fuse_adjacents
qc = QuantumCircuit(1)
qc.x(0)
qc.x(0)
qc.y(0)
optimized_qc = fuse_adjacents(qc)
# optimized_qc now contains only a Y gate
Targeting Native Gate Sets#
Combine passes to convert circuits to hardware-native gate sets:
from qrisp import remove_barriers
qc = QuantumCircuit(3)
qc.swap(0, 1)
qc.cx(1, 2)
qc.barrier()
print("Before:", qc, sep="\n")
Before:
░
qb_0: ─X───────░─
│ ░
qb_1: ─X───■───░─
┌─┴─┐ ░
qb_2: ───┤ X ├─░─
└───┘ ░
pm = PassManager()
pm += convert_to_cz() # CX → CZ + single-qubit gates
pm += combine_single_qubit_gates # Fuse adjacent single-qubit gates
pm += remove_barriers # Remove scheduling barriers
hw_ready_qc = pm.run(qc)
print("After:", hw_ready_qc, sep="\n")
After:
┌───┐ ┌───┐
qb_0: ──────■─┤ H ├─■─┤ H ├─■──────────────
┌───┐ │ ├───┤ │ ├───┤ │ ┌───┐
qb_1: ┤ H ├─■─┤ H ├─■─┤ H ├─■─┤ H ├─■──────
├───┤ └───┘ └───┘ └───┘ │ ┌───┐
qb_2: ┤ H ├─────────────────────────■─┤ H ├
└───┘ └───┘
Built-in Passes#
Qrisp ships with the following circuit transformation passes:
Pass |
Description |
|---|---|
Rearrange SWAP gates for better cancellation later |
|
Cancel adjacent gate–inverse-gate pairs via DAG analysis |
|
Remove gates controlled on |0⟩ states |
|
Fuse consecutive single-qubit gates into one |
|
Commute single-qubit ops past SWAP gates |
|
Convert two-qubit gates (CZ, CY, SWAP) to CX-based form |
|
Convert two-qubit gates (CX, CY, SWAP) to CZ-based form |
|
Recursively dissolve synthesized gates into elementary gates |
|
Synthesize Toffoli gates using Gray-code decomposition |
|
Re-index qubits according to a user-supplied mapping |
|
Remove barrier instructions from the circuit |
|
Resolve SWAP gates by physically permuting qubits |
|
Reverse-parallelize the circuit for reuse in conjugate |