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 |