Plasma-SABRE tutorial#
This tutorial gives a practical overview of the plasma-sabre transpiler for Qrisp, including user-facing passes and the IQM execution flow.
What you will learn#
How to build and inspect a Qrisp circuit
How to compose transpilation pipelines with
PassManagerHow to use layout/routing passes:
plasma_layoutplasma_routevf2pp_layoutmanual_layout
How to use gate conversion passes:
convert_to_czconvert_to_prx
How to run full IQM transpilation via
transpile_to_iqmHow to submit a transpiled circuit to an IQM backend
Setup#
# Core imports
from qrisp import QuantumCircuit, Qubit, Clbit, PassManager, convert_to_cz, convert_to_prx, manual_layout
# IQM imports
from iqm.qrisp_iqm import plasma_layout, plasma_route, vf2pp_layout, transpile_to_iqm
print("Imports successful.")
Output:
WARNING:jax._src.xla_bridge:864: An NVIDIA GPU may be present on this machine, but a CUDA-enabled jaxlib is not installed. Falling back to cpu.
Imports successful.
Demo circuit#
We define a small demo circuit with various two-qubit interactions to exercise the transpiler:
def build_demo_circuit() -> QuantumCircuit:
"""Build a small demo circuit with 2-qubit interactions."""
qc = QuantumCircuit()
# Give logical qubits distinctive names
for i in range(4):
qc.add_qubit(Qubit("original_qb_" + str(i)))
for i in range(4):
qc.add_clbit(Clbit("c" + str(i)))
qc.h(0)
qc.cx(0, 1)
qc.ry(0.7, 2)
qc.cz(1, 2)
qc.cx(2, 3)
qc.s(1)
qc.cy(0, 2)
# Add measurements for execution workflows
qc.measure(qc.qubits, qc.clbits)
return qc
qc = build_demo_circuit()
print(qc)
Output:
┌───┐ ┌─┐
original_qb_0: ───┤ H ├─────■────────────■─────┤M├───
└───┘ ┌─┴─┐ ┌───┐ │ ┌─┐└╥┘
original_qb_1: ───────────┤ X ├─■─┤ S ├──┼──┤M├─╫────
┌─────────┐└───┘ │ └───┘┌─┴─┐└╥┘ ║ ┌─┐
original_qb_2: ┤ Ry(0.7) ├──────■───■──┤ Y ├─╫──╫─┤M├
└─────────┘ ┌─┴─┐└┬─┬┘ ║ ║ └╥┘
original_qb_3: ───────────────────┤ X ├─┤M├──╫──╫──╫─
└───┘ └╥┘ ║ ║ ║
c0: ══════════════════════════╬═══╬══╩══╬═
║ ║ ║
c1: ══════════════════════════╬═══╩═════╬═
║ ║
c2: ══════════════════════════╬═════════╩═
║
c3: ══════════════════════════╩═══════════
1. plasma_layout — Finding the best initial placement#
plasma_layout solves the initial placement problem: given a quantum circuit with logical
qubits and a hardware topology with physical qubits connected by edges, find a mapping
of logical → physical qubits that minimizes the routing cost downstream.
What it does concretely#
The pass assigns each logical qubit in the circuit to a physical qubit on the QPU.
No SWAP gates are inserted; the gate sequence stays the same, only the qubit
indices change. For instance, if the original circuit applies cx(0, 3) and plasma_layout
decides logical qubit 0 should live on physical qubit 2 and logical qubit 3 on physical qubit 1,
the output circuit will contain cx(2, 1) (and will have as many qubits as the hardware
topology, not just the circuit).
Note
The resulting circuit is not necessarily executable yet: it may still need a routing pass that inserts SWAP gates.
Example topology — a “cut ring”#
Throughout this tutorial we use 6 physical qubits connected in a ring with the edge (0, 1) removed:
1 — 2 — 3 — 4 — 5 — 0
This is essentially a chain 1-2-3-4-5-0. Because one edge of the ring is missing,
the layout pass actually has to work: naïvely mapping logical qubit 0 to physical qubit 0 and
logical qubit 1 to physical qubit 1 would place them on opposite ends of the chain, requiring
many SWAPs. plasma_layout finds a better placement.
Relevant parameters#
Parameter |
Default |
Description |
|---|---|---|
|
(required) |
List of |
|
|
More effort → more random candidates and refinement iterations → better layouts but slower compile |
|
|
|
VF2++ fast path#
Before starting the stochastic search, plasma_layout first tries a VF2++ subgraph
isomorphism check. If the circuit’s qubit-interaction graph is already a subgraph of the
hardware topology, the circuit can be mapped with zero routing cost and VF2++ returns
immediately. The stochastic search is only triggered when VF2++ fails — which is the case for
our demo circuit, since the cy(0, 2) gate creates an interaction between qubits 0 and 2 that
doesn’t correspond to any single edge in our cut-ring topology.
# Demonstrate plasma_layout alone: only qubit placement, no SWAPs
connectivity = [
(1, 2), (2, 3), (3, 4), (4, 5), (5, 0)
]
pm_layout_only = PassManager()
pm_layout_only += plasma_layout(connectivity, effort=40)
original = build_demo_circuit()
laid_out = pm_layout_only.run(original)
print("Original circuit (4 qubits):")
print(original)
print(f"\nAfter plasma_layout (now {laid_out.num_qubits()} physical qubits, same gates, remapped indices):")
print(laid_out)
Output:
Original circuit (4 qubits):
┌───┐ ┌─┐
original_qb_0: ───┤ H ├─────■────────────■─────┤M├───
└───┘ ┌─┴─┐ ┌───┐ │ ┌─┐└╥┘
original_qb_1: ───────────┤ X ├─■─┤ S ├──┼──┤M├─╫────
┌─────────┐└───┘ │ └───┘┌─┴─┐└╥┘ ║ ┌─┐
original_qb_2: ┤ Ry(0.7) ├──────■───■──┤ Y ├─╫──╫─┤M├
└─────────┘ ┌─┴─┐└┬─┬┘ ║ ║ └╥┘
original_qb_3: ───────────────────┤ X ├─┤M├──╫──╫──╫─
└───┘ └╥┘ ║ ║ ║
c0: ══════════════════════════╬═══╬══╩══╬═
║ ║ ║
c1: ══════════════════════════╬═══╩═════╬═
║ ║
c2: ══════════════════════════╬═════════╩═
║
c3: ══════════════════════════╩═══════════
After plasma_layout (now 6 physical qubits, same gates, remapped indices):
padding_qb_0: ──────────────────────────────────────
padding_qb_1: ──────────────────────────────────────
┌───┐ ┌─┐
original_qb_0: ───┤ H ├─────■────────────■─────┤M├───
└───┘ ┌─┴─┐ ┌───┐ │ ┌─┐└╥┘
original_qb_1: ───────────┤ X ├─■─┤ S ├──┼──┤M├─╫────
┌─────────┐└───┘ │ └───┘┌─┴─┐└╥┘ ║ ┌─┐
original_qb_2: ┤ Ry(0.7) ├──────■───■──┤ Y ├─╫──╫─┤M├
└─────────┘ ┌─┴─┐└┬─┬┘ ║ ║ └╥┘
original_qb_3: ───────────────────┤ X ├─┤M├──╫──╫──╫─
└───┘ └╥┘ ║ ║ ║
c0: ══════════════════════════╬═══╬══╩══╬═
║ ║ ║
c1: ══════════════════════════╬═══╩═════╬═
║ ║
c2: ══════════════════════════╬═════════╩═
║
c3: ══════════════════════════╩═══════════
2. plasma_route — SWAP insertion#
plasma_route takes a circuit that already has a fixed layout (e.g. from plasma_layout)
and inserts SWAP gates so that every 2-qubit gate acts on physically adjacent qubits.
Relevant parameters#
Parameter |
Default |
Description |
|---|---|---|
|
(required) |
Hardware topology edges |
|
|
More effort → better results, slower compile |
|
|
|
Combined pipeline: plasma_layout → plasma_route#
In practice you always chain the two passes. Make sure depth_weight matches between them.
# Example hardware topology (6 physical qubits):
connectivity = [
(1, 2), (2, 3), (3, 4), (4, 5), (5, 0)
]
pm = PassManager()
pm += plasma_layout(connectivity, effort=40)
pm += plasma_route(connectivity, effort=40)
routed_qc = pm.run(build_demo_circuit())
print(routed_qc)
Output:
padding_qb_0: ─────────────────────────────────────────
padding_qb_1: ─────────────────────────────────────────
┌───┐ ┌─┐
original_qb_0: ───┤ H ├─────■──────────────────■─────┤M├
└───┘ ┌─┴─┐ ┌───┐┌─┐ ┌─┴─┐┌─┐└╥┘
original_qb_1: ───────────┤ X ├─■─┤ S ├┤M├─X─┤ Y ├┤M├─╫─
┌─────────┐└───┘ │ └───┘└╥┘ │ └───┘└╥┘ ║
original_qb_2: ┤ Ry(0.7) ├──■───■───────╫──X───────╫──╫─
└─────────┘┌─┴─┐┌─┐ ║ ║ ║
original_qb_3: ───────────┤ X ├┤M├──────╫──────────╫──╫─
└───┘└╥┘ ║ ║ ║
c0: ═════════════════╬═══════╬══════════╬══╩═
║ ║ ║
c1: ═════════════════╬═══════╩══════════╬════
║ ║
c2: ═════════════════╬══════════════════╩════
║
c3: ═════════════════╩═══════════════════════
3. vf2pp_layout#
vf2pp_layout attempts to embed the circuit’s interaction graph directly into the topology
via VF2++ subgraph isomorphism.
If successful, the circuit can run without any routing SWAPs at all.
Use this when you suspect your circuit connectivity already fits the device graph.
Note: plasma_layout already tries VF2++ internally as a fast path — this standalone pass
is useful when you want to only attempt VF2++. A crucial difference to calling VF2++ from
plasma_layout is that vf2pp_layout will raise an Exception if there is no perfect layout.
plasma_layout will simply proceed with heuristic layout selection.
When does it fail?#
Our demo circuit has the interaction cy(0, 2), i.e. logical qubits 0 and 2 talk to each
other although they are not neighbours on the cut-ring chain 1-2-3-4-5-0. No relabelling
can fix this because the interaction graph contains a “triangle-like” structure that doesn’t
fit into a path — so VF2++ raises an error.
When does it succeed?#
A circuit whose interactions already form a subgraph of the topology will succeed. Below we show both cases.
pm_vf2 = PassManager()
pm_vf2 += vf2pp_layout(connectivity)
# --- Case 1: demo circuit (fails — interaction graph doesn't fit the chain) ---
try:
vf2_qc = pm_vf2.run(build_demo_circuit())
print("VF2++ layout succeeded on demo circuit:")
print(vf2_qc)
except ValueError as err:
print("VF2++ layout failed on demo circuit (expected — cy(0,2) is non-adjacent):")
print(err)
# --- Case 2: a circuit with only path-like interactions (succeeds) ---
print("\n--- Circuit with path interactions ---")
path_qc = QuantumCircuit()
# Give logical qubits distinctive names
for i in range(4):
path_qc.add_qubit(Qubit("original_qb_" + str(i)))
for i in range(4):
path_qc.add_clbit(Clbit("c_" + str(i)))
path_qc.h(0)
path_qc.cx(0, 1) # 0-1
path_qc.cx(1, 2) # 1-2
path_qc.cx(2, 3) # 2-3
path_qc.measure(path_qc.qubits, path_qc.clbits)
print("Path circuit (interactions: 0-1, 1-2, 2-3):")
print(path_qc)
vf2_path_qc = pm_vf2.run(path_qc)
print(f"VF2++ succeeded — mapped to {vf2_path_qc.num_qubits()} physical qubits, zero SWAPs needed:")
print(vf2_path_qc)
Output:
VF2++ layout failed on demo circuit (expected — cy(0,2) is non-adjacent):
VF2++ could not find a matching qubit set for the circuit. The circuit's connectivity graph is not a subgraph of the topology. Consider using 'plasma_layout' and 'plasma_route' for circuits requiring swap insertion.
--- Circuit with path interactions ---
Path circuit (interactions: 0-1, 1-2, 2-3):
┌───┐ ┌─┐
original_qb_0: ┤ H ├──■───────┤M├──────────────
└───┘┌─┴─┐ └╥┘ ┌─┐
original_qb_1: ─────┤ X ├──■───╫──────┤M├──────
└───┘┌─┴─┐ ║ └╥┘┌─┐
original_qb_2: ──────────┤ X ├─╫───■───╫─┤M├───
└───┘ ║ ┌─┴─┐ ║ └╥┘┌─┐
original_qb_3: ────────────────╫─┤ X ├─╫──╫─┤M├
║ └───┘ ║ ║ └╥┘
c_0: ════════════════╩═══════╬══╬══╬═
║ ║ ║
c_1: ════════════════════════╩══╬══╬═
║ ║
c_2: ═══════════════════════════╩══╬═
║
c_3: ══════════════════════════════╩═
VF2++ succeeded — mapped to 6 physical qubits, zero SWAPs needed:
padding_qb_0: ────────────────────────────────
┌───┐ ┌─┐
original_qb_0: ┤ H ├──■───────┤M├──────────────
└───┘┌─┴─┐ └╥┘ ┌─┐
original_qb_1: ─────┤ X ├──■───╫──────┤M├──────
└───┘┌─┴─┐ ║ └╥┘┌─┐
original_qb_2: ──────────┤ X ├─╫───■───╫─┤M├───
└───┘ ║ ┌─┴─┐ ║ └╥┘┌─┐
original_qb_3: ────────────────╫─┤ X ├─╫──╫─┤M├
║ └───┘ ║ ║ └╥┘
padding_qb_1: ────────────────╫───────╫──╫──╫─
║ ║ ║ ║
c_0: ════════════════╩═══════╬══╬══╬═
║ ║ ║
c_1: ════════════════════════╩══╬══╬═
║ ║
c_2: ═══════════════════════════╩══╬═
║
c_3: ══════════════════════════════╩═
4. manual_layout#
manual_layout lets you choose physical qubits explicitly.
Input:
qubit_mapping, where logical qubitimaps to physicalqubit_mapping[i]Mapping must be the same length as circuit qubits, with unique non-negative indices
This is useful when you want deterministic placement (e.g. due to calibration data).
# Map 4 logical qubits -> physical qubits [1, 2, 4, 5]
manual_map = [1, 2, 4, 5]
pm_manual = PassManager()
pm_manual += manual_layout(manual_map)
manual_qc = pm_manual.run(build_demo_circuit())
print(manual_qc)
Output:
padding_qb_0: ──────────────────────────────────────
┌───┐ ┌─┐
original_qb_0: ───┤ H ├─────■────────────■─────┤M├───
└───┘ ┌─┴─┐ ┌───┐ │ ┌─┐└╥┘
original_qb_1: ───────────┤ X ├─■─┤ S ├──┼──┤M├─╫────
└───┘ │ └───┘ │ └╥┘ ║
padding_qb_1: ─────────────────┼────────┼───╫──╫────
┌─────────┐ │ ┌─┴─┐ ║ ║ ┌─┐
original_qb_2: ┤ Ry(0.7) ├──────■───■──┤ Y ├─╫──╫─┤M├
└─────────┘ ┌─┴─┐└┬─┬┘ ║ ║ └╥┘
original_qb_3: ───────────────────┤ X ├─┤M├──╫──╫──╫─
└───┘ └╥┘ ║ ║ ║
c0: ══════════════════════════╬═══╬══╩══╬═
║ ║ ║
c1: ══════════════════════════╬═══╩═════╬═
║ ║
c2: ══════════════════════════╬═════════╩═
║
c3: ══════════════════════════╩═══════════
5. Gate conversion passes#
convert_to_cz#
Converts 2-qubit gates (such as cx, cy, swap) into CZ-based forms.
convert_to_prx#
Converts single-qubit operations into PRX-style decomposition used in IQM-related flows.
These are typically used near the end of a transpilation pipeline.
pm_convert = PassManager()
pm_convert += convert_to_cz()
pm_convert += convert_to_prx
converted_qc = pm_convert.run(build_demo_circuit())
print(converted_qc)
Output:
┌──────────────┐┌────────┐ »
original_qb_0: ┤ R(3π/2,-π/2) ├┤ R(π,0) ├─■──────────────────────────────»
├──────────────┤├────────┤ │ ┌──────────────┐┌────────┐ »
original_qb_1: ┤ R(3π/2,-π/2) ├┤ R(π,0) ├─■─┤ R(3π/2,-π/2) ├┤ R(π,0) ├─■─»
└┬────────────┬┘└────────┘ └──────────────┘└────────┘ │ »
original_qb_2: ─┤ R(0.7,π/2) ├─────────────────────────────────────────■─»
┌┴────────────┴┐┌────────┐ »
original_qb_3: ┤ R(3π/2,-π/2) ├┤ R(π,0) ├────────────────────────────────»
└──────────────┘└────────┘ »
c0: ══════════════════════════════════════════════════════════»
»
c1: ══════════════════════════════════════════════════════════»
»
c2: ══════════════════════════════════════════════════════════»
»
c3: ══════════════════════════════════════════════════════════»
»
« »
«original_qb_0: ─────────────────────────────────────────────────────────»
« ┌────────┐ ┌──────────┐ ┌─┐ »
«original_qb_1: ┤ R(π,0) ├──┤ R(π,π/4) ├──────────────┤M├────────────────»
« └────────┘ ├──────────┤ ┌──────────┐└╥┘┌──────────────┐»
«original_qb_2: ────■───────┤ R(π,π/2) ├──┤ R(π,π/4) ├─╫─┤ R(3π/2,-π/2) ├»
« │ ┌─┴──────────┴─┐└┬────────┬┘ ║ └─────┬─┬──────┘»
«original_qb_3: ────■─────┤ R(3π/2,-π/2) ├─┤ R(π,0) ├──╫───────┤M├───────»
« └──────────────┘ └────────┘ ║ └╥┘ »
« c0: ═══════════════════════════════════════╬════════╬════════»
« ║ ║ »
« c1: ═══════════════════════════════════════╩════════╬════════»
« ║ »
« c2: ════════════════════════════════════════════════╬════════»
« ║ »
« c3: ════════════════════════════════════════════════╩════════»
« »
« ┌─┐┌────────┐ »
«original_qb_0: ───────────■─────────────────┤M├┤ gphase ├──────────»
« │ └╥┘└────────┘ »
«original_qb_1: ───────────┼──────────────────╫─────────────────────»
« ┌────────┐ │ ┌──────────────┐ ║ ┌────────┐┌────────┐»
«original_qb_2: ┤ R(π,0) ├─■─┤ R(3π/2,-π/2) ├─╫─┤ R(π,0) ├┤ R(π,0) ├»
« └────────┘ └──────────────┘ ║ └────────┘└────────┘»
«original_qb_3: ──────────────────────────────╫─────────────────────»
« ║ »
« c0: ══════════════════════════════╩═════════════════════»
« »
« c1: ════════════════════════════════════════════════════»
« »
« c2: ════════════════════════════════════════════════════»
« »
« c3: ════════════════════════════════════════════════════»
« »
«
«original_qb_0: ───────────────
«
«original_qb_1: ───────────────
« ┌──────────┐┌─┐
«original_qb_2: ┤ R(π,π/4) ├┤M├
« └──────────┘└╥┘
«original_qb_3: ─────────────╫─
« ║
« c0: ═════════════╬═
« ║
« c1: ═════════════╬═
« ║
« c2: ═════════════╩═
«
« c3: ═══════════════
«
6. Tuning effort and depth_weight#
Both plasma_layout and plasma_route accept two knobs that let you trade off compilation
time against result quality, and gate count against circuit depth:
Parameter |
Effect |
|---|---|
|
Controls how many random seeds / candidates the router explores. Higher values find better solutions but take longer. |
|
Steers the optimization target on a scale from −1 (minimize gate/SWAP count) through 0 (balanced) to +1 (minimize depth). |
To see these in action, we compile a non-trivial circuit — a 5-bit quantum adder — onto a 4 × 4 square-grid topology and compare three settings.
The decompose pass#
Before layout and routing, the compiled adder circuit contains multi-controlled gates (> 2
qubits). These cannot be placed directly onto hardware. The decompose pass recursively
breaks down any gate satisfying a predicate (here: more than 2 qubits) into 1- and 2-qubit
gates. We include it in the pipeline so the router only sees hardware-compatible operations.
from qrisp import QuantumFloat, decompose
# Build a 5-bit quantum adder circuit
a = QuantumFloat(5)
b = QuantumFloat(5)
a += b
qc = a.qs.compile()
# 4x4 square grid coupling map
N = 4
connectivity = []
for i in range(N**2):
if i % N:
connectivity.append((i, i - 1))
if i > N:
connectivity.append((i, i - N))
def route_with_settings(depth_weight, effort):
pm = PassManager()
pm += decompose(decompose_predicate = lambda op: op.num_qubits > 2)
pm += plasma_layout(connectivity=connectivity, depth_weight=depth_weight, effort=effort)
pm += plasma_route(connectivity=connectivity, depth_weight=depth_weight, effort=effort)
compiled = pm.run(qc)
return compiled
# --- Run 1: depth_weight = -1 (minimize gate count) ---
qc_gateopt = route_with_settings(depth_weight=-1, effort=10)
print("depth_weight = -1, effort = 10 (minimize gate count)")
print(f" CNOT depth : {qc_gateopt.cnot_depth()}")
print(f" Gate counts: {qc_gateopt.count_ops()}")
# --- Run 2: depth_weight = +1 (minimize depth) ---
qc_depthopt = route_with_settings(depth_weight=1, effort=10)
print("\ndepth_weight = +1, effort = 10 (minimize depth)")
print(f" CNOT depth : {qc_depthopt.cnot_depth()}")
print(f" Gate counts: {qc_depthopt.count_ops()}")
# --- Run 3: depth_weight = +1, higher effort ---
qc_depthopt_hi = route_with_settings(depth_weight=1, effort=1000)
print("\ndepth_weight = +1, effort = 1000 (minimize depth, try harder)")
print(f" CNOT depth : {qc_depthopt_hi.cnot_depth()}")
print(f" Gate counts: {qc_depthopt_hi.count_ops()}")
Output:
depth_weight = -1, effort = 10 (minimize gate count)
CNOT depth : 55
Gate counts: {'h': 10, 'cx': 50, 'p': 53, 'swap': 9}
depth_weight = +1, effort = 10 (minimize depth)
CNOT depth : 52
Gate counts: {'h': 10, 'cx': 50, 'p': 53, 'swap': 10}
depth_weight = +1, effort = 1000 (minimize depth, try harder)
CNOT depth : 46
Gate counts: {'h': 10, 'cx': 50, 'p': 53, 'swap': 11}
What to observe#
``depth_weight = -1`` produces the fewest SWAP gates (lowest total gate count) but the deepest circuit — the router packs qubits tightly even if that serialises operations.
``depth_weight = +1`` trades extra SWAPs for a shallower circuit: the CNOT depth drops, while the SWAP / gate count increases.
Raising ``effort`` to 1000 at
depth_weight = +1pushes the depth even lower — at the cost of yet more SWAPs and longer compilation time. The router explores more candidates and finds increasingly aggressive parallelisation strategies.
In general, use depth_weight = -1 when gate count (and thus error rate) matters most, and
depth_weight = +1 when circuit duration (i.e. thermal decay) on hardware is the bottleneck.
effort controls how long you’re willing to wait for a better solution.
7. End-to-end IQM transpilation: transpile_to_iqm#
transpile_to_iqm wraps the full plasma-sabre pipeline — layout, routing, gate conversion,
and several additional optimization passes — into a single call. Because it bundles these
strong optimizations together, it should be considered the default function for
production-level transpilation when targeting IQM hardware.
Note
The public function name is transpile_to_iqm (lowercase).
# Local transpilation example against a known coupling map
iqm_ready_qc = transpile_to_iqm(build_demo_circuit(), connectivity=connectivity)
print(iqm_ready_qc)
Output:
padding_qb_0: ─────────────────────────────────────────────────────────────»
»
padding_qb_10: ─────────────────────────────────────────────────────────────»
┌─────────────┐ ┌────────────┐ ┌─┐ »
original_qb_1: ┤ R(π/2,-π/2) ├─■─┤ R(π/2,π/2) ├─■────┤M├────────────────────»
├─────────────┤ │ └────────────┘ │ └╥┘ ┌────────────┐ »
original_qb_0: ┤ R(π/2,-π/2) ├─■────────────────┼─────╫──■─┤ R(π/2,π/2) ├─■─»
└─────────────┘ │ ║ │ └────────────┘ │ »
padding_qb_1: ─────────────────────────────────┼─────╫──┼────────────────┼─»
│ ║ │ │ »
padding_qb_3: ─────────────────────────────────┼─────╫──┼────────────────┼─»
┌────────────┐ │ ║ │ ┌──────────┐ │ »
original_qb_2: ─┤ R(0.7,π/2) ├──────────────────■──■──╫──┼──┤ R(π/2,0) ├──┼─»
┌┴────────────┤ │ ║ │ ┌┴──────────┴┐ │ »
original_qb_3: ┤ R(π/2,-π/2) ├─────────────────────■──╫──■─┤ R(π/2,π/2) ├─■─»
└─────────────┘ ║ └────────────┘ »
padding_qb_9: ───────────────────────────────────────╫─────────────────────»
║ »
padding_qb_7: ───────────────────────────────────────╫─────────────────────»
║ »
padding_qb_6: ───────────────────────────────────────╫─────────────────────»
║ »
padding_qb_2: ───────────────────────────────────────╫─────────────────────»
║ »
padding_qb_5: ───────────────────────────────────────╫─────────────────────»
║ »
padding_qb_8: ───────────────────────────────────────╫─────────────────────»
║ »
padding_qb_4: ───────────────────────────────────────╫─────────────────────»
║ »
padding_qb_11: ───────────────────────────────────────╫─────────────────────»
║ »
c0: ═══════════════════════════════════════╬═════════════════════»
║ »
c1: ═══════════════════════════════════════╩═════════════════════»
»
c2: ═════════════════════════════════════════════════════════════»
»
c3: ═════════════════════════════════════════════════════════════»
»
«
« padding_qb_0: ──────────────────────────────────────────────────────
«
«padding_qb_10: ──────────────────────────────────────────────────────
«
«original_qb_1: ──────────────────────────────────────────────────────
« ┌─────────────┐ ┌─┐
«original_qb_0: ┤ R(π/2,-π/2) ├─■───────────────┤M├───────────────────
« └─────────────┘ │ └╥┘
« padding_qb_1: ────────────────┼────────────────╫────────────────────
« │ ║
« padding_qb_3: ────────────────┼────────────────╫────────────────────
« │ ║ ┌───────────┐┌─┐
«original_qb_2: ────────────────┼────────────────╫──■─┤ R(π/2,-π) ├┤M├
« ┌─────────────┐ │ ┌────────────┐ ║ │ └────┬─┬────┘└╥┘
«original_qb_3: ┤ R(π/2,-π/2) ├─■─┤ R(π/2,π/2) ├─╫──■──────┤M├──────╫─
« └─────────────┘ └────────────┘ ║ └╥┘ ║
« padding_qb_9: ─────────────────────────────────╫──────────╫───────╫─
« ║ ║ ║
« padding_qb_7: ─────────────────────────────────╫──────────╫───────╫─
« ║ ║ ║
« padding_qb_6: ─────────────────────────────────╫──────────╫───────╫─
« ║ ║ ║
« padding_qb_2: ─────────────────────────────────╫──────────╫───────╫─
« ║ ║ ║
« padding_qb_5: ─────────────────────────────────╫──────────╫───────╫─
« ║ ║ ║
« padding_qb_8: ─────────────────────────────────╫──────────╫───────╫─
« ║ ║ ║
« padding_qb_4: ─────────────────────────────────╫──────────╫───────╫─
« ║ ║ ║
«padding_qb_11: ─────────────────────────────────╫──────────╫───────╫─
« ║ ║ ║
« c0: ═════════════════════════════════╬══════════╩═══════╬═
« ║ ║
« c1: ═════════════════════════════════╬══════════════════╬═
« ║ ║
« c2: ═════════════════════════════════╬══════════════════╩═
« ║
« c3: ═════════════════════════════════╩════════════════════
«
8. Actual IQM backend call (with token placeholder)#
This section demonstrates real hardware submission.
Install IQM client package (
iqm-client) if neededSet your token and server URL
Fetch architecture, transpile, submit
If token is left as placeholder, the code prints instructions and skips submission.
from iqm.qrisp_iqm import IQMBackend, create_iqm_pass_manager
token = "YOUR_TOKEN_HERE"
server_url = "https://resonance.iqm.tech"
if token == "YOUR_TOKEN_HERE":
print("Set your real IQM token in `token` to run this cell.")
else:
garnet = IQMBackend(device_instance="garnet", # Select garnet
server_url=server_url,
token=token, # Authenticate
pass_manager = PassManager()) # Create an empty pass manager to ensure circuits are passed to the backend as is
iqm_connectivity = garnet.connectivity
garnet.pm += create_iqm_pass_manager(connectivity=iqm_connectivity,
effort=100,
depth_weight=0.0,)
result_counts = garnet.run(build_demo_circuit(), shots=100)
print("IQM result counts:")
print(result_counts)
Output (with a valid token):
IQM result counts:
{'0000': 41, '0011': 5, '0100': 2, '0110': 2, '0111': 2, '1001': 2, '1010': 1, '1011': 1, '1100': 2, '1101': 7, '1110': 35}