> "Your circuit is a wish. The transpiler is what actually happens."
Prerequisites
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
Learning Objectives
- Describe the four problems the transpiler solves and why each one is forced by hardware rather than chosen.
- Read a backend's target: basis gates, coupling map, and degree distribution.
- Explain why routing inserts SWAPs and quantify the cost on a circuit that forces them.
- Name the pass manager's six stages and the kind of pass each contains.
- Compare optimization levels 0 through 3 on both two-qubit gate count and transpilation time, and choose deliberately.
- Select layout and routing methods explicitly and measure the difference.
- Write a custom transformation pass and run it in a PassManager.
- Explain why seed_transpiler must be recorded with every result.
In This Chapter
- Overview
- Learning Paths
- 10.1 The Four Problems
- 10.2 Basis Translation
- 10.3 Routing: The Cost That Surprises People
- 10.4 The Pass Manager
- 10.5 Optimization Levels
- 10.6 Layout and Routing Methods
- 10.7 Reading the Diff
- 10.8 Writing a Custom Pass
- 10.9 The Seed
- 10.10 Transpile Once, Bind Many — Revisited
- 10.11 Summary
Chapter 10: Transpilation
"Your circuit is a wish. The transpiler is what actually happens."
Overview
This is the chapter Part I kept deferring to.
Since Chapter 2 you have known that the circuit you write is not the circuit that runs. Now you find out exactly what happens in between, why it happens, and — the part that matters most — how much control you have over it. The answer is: a great deal, and the difference between a default transpilation and a deliberate one is routinely a factor of two in the thing that determines whether your circuit works.
The transpiler solves four problems, and none of them are optional:
- Your gates do not exist. Hardware implements a small basis — on current IBM devices,
{rz, sx, x, ecr}. Yourhandcxandccxmust be rewritten. - Your qubits are abstract. They must be assigned to physical qubits, which differ in quality (Chapter 4's Case Study 1 measured an eightfold difference from this alone).
- Your qubits are not all connected. A two-qubit gate between non-adjacent physical qubits requires moving the information, and each move costs three CNOTs.
- Your circuit is longer than it needs to be. Optimization passes shorten it, and shorter means less error.
Problem 3 is the one that will surprise you. This chapter measures a five-qubit circuit whose ten logical CNOTs become thirty-four two-qubit gates under a bad layout and eighteen under a good one — nearly a factor of two, from nothing but compiler settings.
The chapter also establishes something you must carry into every experiment you run: transpilation is not deterministic unless you make it so. The same circuit, same device, same optimization level, with four different random seeds, produced two-qubit counts of 18, 18, 20, and 21. Record the seed.
In this chapter, you will learn to:
- Name the four problems and why hardware forces each.
- Read a backend's target: basis gates, coupling map, connectivity.
- Quantify routing cost on a circuit that forces SWAPs.
- Name the pass manager's six stages.
- Compare optimization levels on gate count and transpilation time.
- Choose layout and routing methods explicitly, and measure the difference.
- Write a custom pass.
- Explain why
seed_transpilerbelongs in every recorded result.
Learning Paths
How to read this chapter by track. - 🔰 Beginner — §10.1, §10.2, §10.3, and §10.5. Use optimization level 2 or 3 and move on. - 🔬 Researcher — §10.9 on seeds is non-negotiable for reproducible results, and §10.7's diff technique is how you document what actually ran. - 🤖 Quantum ML — §10.10's transpile-once pattern, and §10.3 for why your ansatz's entanglement pattern should match the device's topology. - 🏗️ Quantum Engineer — the whole chapter twice. §10.6 (layout and routing methods) and §10.8 (custom passes) are the professional core. - 🔐 Security — skim §10.1 and §10.3. The routing overhead is a large part of why resource estimates for Shor's algorithm are so much worse than gate counts suggest (Chapter 23).
10.1 The Four Problems
Start with the target — the machine-readable description of what a device can actually do.
from qiskit_ibm_runtime.fake_provider import FakeSherbrooke
backend = FakeSherbrooke()
target = backend.target
print(f"qubits: {backend.num_qubits}")
print(f"operations: {sorted(target.operation_names)}")
qubits: 127
operations: ['delay', 'ecr', 'for_loop', 'id', 'if_else', 'measure',
'reset', 'rz', 'switch_case', 'sx', 'x']
Everything the device can do is in that list. No h. No cx. No ry. Four real gates
(ecr, rz, sx, x), plus measurement, reset, delay, and the control-flow constructs from
Chapter 9.
That is problem 1, and it is not a limitation to be engineered around — it is what the physics provides. A superconducting qubit responds to microwave pulses of particular shapes, and those pulses implement those gates. Everything else is composition.
Problems 2 and 3 come from the topology.
coupling = target.build_coupling_map()
print(f"edges: {len(coupling.get_edges())}")
print(f"first few: {coupling.get_edges()[:8]}")
edges: 144
first few: [(1, 0), (1, 2), (3, 2), (4, 3), (4, 15), (5, 4), (6, 5), (7, 6)]
127 qubits, 144 directed edges — 72 actual connections. For comparison, all-to-all connectivity on 127 qubits would be $\binom{127}{2} = 8{,}001$ pairs. You have 0.90% of them.
This is the heavy-hexagonal lattice IBM uses. Each qubit connects to two or three neighbors, and that sparsity is deliberate: fewer connections means less crosstalk and better coherence, at the cost of harder routing. Other technologies trade differently — trapped ions offer all-to-all connectivity and pay in gate speed (Chapter 17).
⚛️ The Physics Underneath — Why not just connect everything?
On superconducting hardware, a coupling between two qubits is a physical circuit element, and it is always on. Every connection is a channel for unwanted interaction: crosstalk when you drive one qubit and its neighbor responds, and frequency crowding when nearby qubits must have distinguishable resonances.
The heavy-hex lattice is an engineering compromise — enough connectivity to be useful, sparse enough that the qubits stay clean. IBM moved to it from a denser square lattice precisely because the denser one had worse errors.
So the routing overhead this chapter measures is not a defect. It is the price paid for the low gate error rates that make the device usable at all, and it is why a circuit's entanglement pattern should match the hardware's topology when you have a choice (Chapter 8 §8.7).
10.2 Basis Translation
The first thing the transpiler does with your gates: rewrite them.
You have seen this since Chapter 2. h becomes $R_z\sqrt{X}R_z$; cx becomes ecr plus
single-qubit corrections. Chapter 6 §6.5 showed the resulting QASM, including the exporter's inline
definition of ecr in terms of gates you know.
The important accounting, from Chapter 3 §3.8 and Chapter 8 §8.8:
| Gate | Real cost |
|---|---|
rz(θ) |
free — virtual, zero duration, zero error |
sx, x |
one pulse each |
| any single-qubit gate | 3 rz + 2 sx = 2 pulses, regardless of complexity |
ecr |
one two-qubit operation, ~100× the error of a single-qubit gate |
Basis translation is essentially free to reason about, because that last column does not depend on what you wrote. The interesting costs are all in layout and routing.
10.3 Routing: The Cost That Surprises People
Here is the measurement that should change how you write circuits.
Take a five-qubit circuit that entangles every pair — ten CNOTs, all-to-all:
qc = QuantumCircuit(5)
for i in range(5):
for j in range(i + 1, 5):
qc.cx(i, j)
print(dict(qc.count_ops())) # {'cx': 10}
Ten two-qubit gates. Now transpile it for a device whose qubits have at most three neighbors:
level 0: depth 162 ecr 34 layout [4, 3, 0, 2, 1]
level 1: depth 78 ecr 28 layout [62, 61, 58, 60, 59]
level 2: depth 67 ecr 18 layout [58, 53, 59, 61, 60]
level 3: depth 69 ecr 18 layout [58, 61, 59, 53, 60]
Ten logical CNOTs became thirty-four physical two-qubit gates at level 0. The extra twenty-four are SWAPs — three CNOTs each, eight SWAPs — inserted to bring qubits together that the topology keeps apart.
And at level 2 the same circuit needs eighteen. Same circuit, same device, same answer — nearly half the two-qubit gates, purely from better layout and routing.
⚠️ Common Pitfall — Counting gates in the circuit you wrote.
"My algorithm needs ten CNOTs" is a statement about your source code, not about what will execute. On this device the honest range is 18 to 34, depending on compiler settings you may not have thought about.
Chapter 1 §1.5 gave a budget of a few hundred two-qubit gates. That budget is in transpiled gates. A circuit that looks like it fits may not, and the factor is routinely 2–3× for circuits whose connectivity does not match the device.
Always measure the transpiled count, and measure it on the device you will actually run on:
python isa = pm.run(qc) two_q = sum(v for k, v in isa.count_ops().items() if k in ("ecr", "cz", "cx"))
Why SWAPs cost three CNOTs
Chapter 4 §4.6 established the identity: cx(0,1); cx(1,0); cx(0,1) equals SWAP. There is no cheaper
construction, and on hardware the SWAP is not even a native gate — the transpiler emits the three
CNOTs directly, each of which becomes an ecr plus corrections.
So the arithmetic is brutal: moving a qubit one step costs as much as three of your most expensive operations. A circuit needing a gate between qubits five steps apart pays twelve CNOTs to set it up — four SWAPs, not five, because a two-qubit gate needs its operands adjacent, not co-located. The general form is $3(d-1)$ CNOTs at graph distance $d$, derived in this section's Math Aside.
This is the single largest hidden cost in quantum programming, and it is why Chapter 8 §8.7 argued
for linear and pairwise entanglement patterns.
📐 Math Aside — Why three, and why a route of length $d$ costs $3(d-1)$.
The identity is checkable on basis states alone, because all three gates are permutation matrices in the computational basis: if they agree on $|00\rangle, |01\rangle, |10\rangle, |11\rangle$ they agree everywhere by linearity. Track $|a, b\rangle$ with $\oplus$ meaning XOR:
$$|a,\, b\rangle \xrightarrow{\ \text{CX}_{0\to1}\ } |a,\, a \oplus b\rangle > \xrightarrow{\ \text{CX}_{1\to0}\ } |b,\, a \oplus b\rangle > \xrightarrow{\ \text{CX}_{0\to1}\ } |b,\, a\rangle$$
The middle step is the one worth pausing on: the new value of qubit 0 is $a \oplus (a \oplus b) = (a \oplus a) \oplus b = b$, because $a \oplus a = 0$. The first CNOT plants a copy of the XOR, and the second uses it to erase the original. That is the whole trick, and it is the same trick as the classical three-XOR swap.
Two CNOTs cannot do it. This is a theorem rather than a failure of imagination: every two-qubit unitary has a canonical form with three interaction coordinates, an $n$-CNOT circuit can only reach unitaries with at least $3-n$ of them zero, and SWAP sits at the maximally non-local point where all three are equal and none is zero. Three is both the general upper bound and SWAP's exact cost.
Now the route. Two qubits at graph distance $d$ have $d$ edges between them along the shortest path. Sliding one of them toward the other reduces the distance by exactly one per SWAP, and they become adjacent — which is all a two-qubit gate requires — when the distance reaches 1, not 0. So the cost is
$$ 3(d-1) \text{ CNOTs}, \qquad \text{not } 3d. $$
The "three CNOTs per step" rule of thumb quoted above is therefore a safe upper bound rather than the exact count. Two qubits five hops apart need four SWAPs and twelve CNOTs; you never pay for the last step, because the gate happens instead of it. On a small circuit that off-by-one is one SWAP out of a handful. On a wide one, where the router is walking several qubits at once, it is worth having right.
One more saving, and it is larger than the off-by-one. A naive scheme would swap the qubits back afterwards, doubling the bill to $6(d-1)$. Real routers never do. They leave every qubit wherever it lands and record the resulting permutation, which is exactly what
final_index_layout()reports and why §10.7 insists you read it.
Which of the four stages actually grows the count
Four problems, four kinds of pass — but they do not contribute equally to the number that matters. Exactly one of them can add a two-qubit gate.
- Basis translation rewrites each gate into the device's alphabet. It is a fixed substitution: one
cxbecomes oneecrplus single-qubit corrections, and any single-qubit gate becomes two pulses regardless of what it was (§10.2). It changes the names and the pulse count. It cannot change how many two-qubit operations there are. - Layout assigns logical qubits to physical ones. It is a relabelling. It changes which qubits bear the cost — Chapter 4's Case Study 1 measured an eightfold fidelity swing from this alone — and it changes how much routing will be needed downstream, but it inserts nothing.
- Optimization only ever removes or merges.
- Routing inserts SWAPs. It is the only additive stage in the pipeline.
That is an argument. Here is the measurement, taken by logging every pass in the preset pipeline and
recording the last state that still contains un-decomposed swap instructions — the router's output,
before translation breaks each one into three CNOTs:
level | swaps inserted | 2q equivalent | final 2q | removed by optimization
------|----------------|---------------|----------|------------------------
0 | 8 | 34 | 34 | 0
1 | 6 | 28 | 28 | 0
2 | 4 | 22 | 18 | 4
3 | 4 | 22 | 18 | 4
Read the middle column against §10.3's headline table and every number lines up. The router handed translation a circuit containing exactly $10 + 3 \times 8 = 34$ two-qubit gates' worth of work at level 0, and $10 + 3 \times 4 = 22$ at level 2. Nothing else added anything.
This also settles where the famous 34-to-18 improvement comes from, which is not where most people assume:
34 - 18 = 16 gates saved, and they split
12 from better layout and routing (8 swaps -> 4 swaps, x3)
4 from the optimization stage (22 -> 18)
Three-quarters of the win is layout and routing; one quarter is optimization. And note which
levels recover the last four: levels 2 and 3 — the two that add TwoQubitPeepholeOptimization, which
collects a two-qubit block and resynthesises it, and can therefore absorb a SWAP into the CNOT next to
it. Levels 0 and 1 removed nothing at all.
The single-qubit cost tracks the same story, because each inserted CNOT drags its basis-translation corrections along:
level | rz | sx | x | real pulses (sx + x) | ecr
------|------|------|-----|----------------------|-----
0 | 234 | 114 | 10 | 124 | 34
1 | 75 | 50 | 7 | 57 | 28
2 | 64 | 40 | 4 | 44 | 18
3 | 65 | 41 | 3 | 44 | 18
Level 0 runs 2.8× the pulses of level 2 as well as 1.9× the two-qubit gates. The rz column is
free (virtual Z, zero duration, zero error — Chapter 31 measured rz at 0.0 ns), which is why the
table separates it out; the sx and x columns are what the control electronics actually plays.
The comparison that makes the case
Run the same experiment on a linear chain — nearest-neighbor CNOTs only, which is what those entanglement patterns produce:
all-to-all, 5 qubits linear chain, 5 qubits
level | 2q gates | vs logical level | 2q gates | vs logical
------|----------|----------- ------|----------|-----------
0 | 34 | 3.4x 0 | 4 | 1.0x
1 | 28 | 2.8x 1 | 4 | 1.0x
2 | 18 | 1.8x 2 | 4 | 1.0x
3 | 18 | 1.8x 3 | 4 | 1.0x
Four logical CNOTs, four physical gates, at every optimization level. Zero routing overhead.
And the gap does not close with size — it widens:
n | logical | all-to-all | ratio | chain | ratio
---|---------|------------|-------|-------|------
3 | 3 | 6 | 2.0x | 2 | 1.0x
4 | 6 | 12 | 2.0x | 3 | 1.0x
5 | 10 | 18 | 1.8x | 4 | 1.0x
6 | 15 | 33 | 2.2x | 5 | 1.0x
7 | 21 | 44 | 2.1x | 6 | 1.0x
Routing overhead is not a constant tax. It scales with how badly your circuit's connectivity mismatches the device's, and a circuit that matches pays nothing at all. Chapter 8's entanglement pattern was not a stylistic choice; it is the difference between 1.0× and 3.4×.
📊 What the Numbers Say — the residual test: subtract, then divide by three.
Chapter 39 §39.6 uses a one-line diagnostic that is worth learning here, because it turns a gate count into a diagnosis. Take the transpiled two-qubit count, subtract the logical count, and see whether the remainder divides by three.
A residual that divides by three exactly is the arithmetic signature of pure SWAP insertion and of nothing else, because SWAPs arrive in units of three CNOTs. Run it across the book's tables:
```text source logical transpiled residual /3? swaps
§10.3 all-to-all(5), level 0 10 34 24 yes 8 §10.3 all-to-all(5), level 1 10 28 18 yes 6 §10.3 all-to-all(5), level 2 10 18 8 NO - Case Study 1 full(6), level 0 30 114 84 yes 28 Case Study 1 full(6), level 1 30 90 60 yes 20 Case Study 1 full(6), level 2 30 80 50 NO - Case Study 1 circular(6), level 1 12 42 30 yes 10 Case Study 1 circular(6), level 2 12 40 28 NO - Ch.39 §39.6 EffSU2(14), best 28 49 21 yes 7 Ch.39 §39.6 EffSU2(14), worst 28 112 84 yes 28 §10.3 linear chain(5), any level 4 4 0 yes 0 ```
Every level-0 and level-1 residual divides by three. Not one level-2 residual does. Ch.39's sweep was run at level 1, which is why all three of its residuals are clean.
That is the diagnostic. A clean multiple of three means what you are looking at is routing and only routing — the optimizer has not touched the two-qubit structure, and the number of SWAPs is the residual divided by three. A residual that does not divide by three means the optimization stage has been inside the routing output, merging SWAPs into neighbouring blocks, and you can no longer read the SWAP count off the total.
A residual of zero is the case you want: your circuit fit the topology and paid nothing. That is the linear chain, at every level and every width.
The floor nobody can reach
How few SWAPs could a perfect router use on the five-qubit all-to-all circuit? The question is worth asking because the answer is honest about how much of the 18 is unavoidable.
Chapter 29 §29.2 records that the heavy-hex lattice has girth 12 — its shortest cycle is twelve qubits long. So any five physical qubits you pick induce a subgraph containing no cycle at all, which makes it a forest: at most four edges among the five chosen qubits.
That bounds what you get for free. Of the $\binom{5}{2} = 10$ pairs your circuit needs, at most four are ever adjacent at any one moment. Each SWAP moves you to a new placement with a new set of at most four adjacent pairs, so covering all ten pairs needs at least three distinct placements, which needs at least two SWAPs — a floor of $10 + 3 \times 2 = 16$ two-qubit gates.
The router found 18. The gap between the bound we can prove (16) and the result we can achieve (18) is not slack in the argument; it is the NP-hardness showing up as ignorance. Nobody has computed the optimum for this instance, and §10.6's honest assessment is the general form of the same point: every routing method in Qiskit is a heuristic, and none of them knows how far from optimal it is either.
What the bound does settle: the 18 is not mostly waste. A circuit demanding all-to-all connectivity on a girth-12 lattice is going to pay, and the compiler has got within a couple of gates of a bound that is itself probably not tight. The 34 was waste. The 18 is mostly physics.
10.4 The Pass Manager
The transpiler is not a monolith. It is an ordered pipeline of passes, grouped into six stages.
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
pm = generate_preset_pass_manager(optimization_level=2, backend=backend, seed_transpiler=42)
for stage in ("init", "layout", "routing", "translation", "optimization", "scheduling"):
tasks = getattr(pm, stage)
names = [type(t).__name__ for t in tasks.to_flow_controller().tasks]
print(f" {stage:<14} {names[:5]}")
init ['UnitarySynthesis', 'HighLevelSynthesis', 'BasisTranslator',
'ElidePermutations', 'RemoveDiagonalGatesBeforeMeasure']
layout ['SetLayout', 'ConditionalController', 'ConditionalController', ...]
routing ['CheckMap', 'ConditionalController', ..., 'FilterOpNodes']
translation ['UnitarySynthesis', 'HighLevelSynthesis', 'BasisTranslator',
'CheckGateDirection', 'ConditionalController']
optimization ['TwoQubitPeepholeOptimization', 'Size', 'Depth', 'FixedPoint', ...]
scheduling ['ContainsInstruction', 'ConditionalController', 'InstructionDurationCheck', ...]
| Stage | Decides |
|---|---|
| init | normalization: expand high-level objects, drop provably useless gates |
| layout | which physical qubit each logical qubit maps to |
| routing | where to insert SWAPs so two-qubit gates act on connected qubits |
| translation | rewriting into the device's basis gates |
| optimization | cancel, merge, resynthesize |
| scheduling | insert delays and timing (optional; Chapter 29) |
Two structural notes worth having.
FixedPoint and DoWhileController in the optimization stage mean the optimizer iterates: it
applies its passes repeatedly until the circuit stops shrinking. That is why higher optimization
levels cost more time — they are not doing different things so much as doing them until convergence.
ConditionalController everywhere means most passes only run if a check says they are needed.
The layout stage, for instance, tries a cheap layout first and escalates only if that one fails or
scores poorly.
10.5 Optimization Levels
Four presets, and the differences are larger than the naming suggests.
On the all-to-all circuit from §10.3, which forces routing:
| Level | Depth | Two-qubit gates | Time |
|---|---|---|---|
| 0 | 162 | 34 | fastest |
| 1 | 78 | 28 | fast |
| 2 | 67 | 18 | moderate |
| 3 | 69 | 18 | slowest |
On a efficient_su2(12, reps=3) ansatz, whose linear entanglement already matches the topology:
| Level | Depth | Two-qubit gates | Time |
|---|---|---|---|
| 0 | 185 | 33 | 1.5 ms |
| 1 | 73 | 33 | 4.1 ms |
| 2 | 85 | 33 | 4.0 ms |
| 3 | 73 | 33 | 7.7 ms |
Read those two tables together, because they say different things.
When routing is needed, the level matters enormously — 34 gates versus 18 is the difference between a circuit that works and one that does not.
When routing is not needed, the level barely matters for two-qubit count (33 at every level) and matters only for depth. And note the anomaly: level 2 produced a deeper circuit than level 1 (85 versus 73) on this circuit. Higher is not monotonically better.
⚠️ Common Pitfall — Level 3 is not "level 1 but better."
The levels differ in which algorithms they use, not merely in how hard they try. A higher level can produce a worse result on a particular circuit, and the anomaly above is a measured example.
The practical rules:
- Level 0: use when you need to know exactly what ran — calibration experiments, benchmarks where the transpiler must not quietly improve your circuit. Never for production.
- Level 1: a reasonable default for iteration. Not safe for a result you will report — Chapter 4's Case Study 1 showed it walking into a dead qubit.
- Level 2: the sensible default for real work. Noise-aware layout, good routing.
- Level 3: try it, measure it, and use it if it wins. It usually does; it is not guaranteed to.
Measure, do not assume. Transpiling at all four levels costs milliseconds and tells you which one your circuit actually likes.
What levels 2 and 3 actually differ in
"Level 3 tries harder" is the folk description and it is not what is happening. The two levels run different pipelines, and the difference is small enough to name exactly. From Chapter 28 §28.4's pass lists:
level 1: Optimize1qGatesDecomposition, InverseCancellation, ...
level 2: + TwoQubitPeepholeOptimization, RemoveIdentityEquivalent,
CommutativeCancellation
level 3: same passes as level 2, but MinimumPoint instead of FixedPoint
for the convergence loop, plus VF2PostLayout and ApplyLayout
Two changes, and both are structural rather than a matter of effort.
FixedPoint versus MinimumPoint is a stopping rule. FixedPoint halts the optimization loop
the first time the tracked metric fails to improve. MinimumPoint keeps going past a plateau and
returns the best circuit it saw rather than the last one. That matters because the loop is not
monotone: TwoQubitPeepholeOptimization resynthesises a two-qubit block into something that can be
temporarily larger, and only on the next iteration does CommutativeCancellation find the
cancellations the resynthesis exposed. A metric that goes down, up, then down again defeats a rule
that stops the first time it fails to move.
VF2PostLayout re-examines the layout after routing, scoring candidate qubit sets by measured
error rates rather than by connectivity. It is not more iterations of level 2's search. It is a
different search.
So how much do they actually differ in practice? Chapter 28 measured it on five circuits × eight seeds:
QFT(5) differed in 0/8 seeds
QFT(8) differed in 6/8 seeds
Grover-ish(5) differed in 0/8 seeds
Grover-ish(7) differed in 8/8 seeds
EffSU2(6, r=4) differed in 0/8 seeds
TOTAL: levels 2 and 3 differed in 14 of 40 circuit-seed pairs.
Of the 14 differing trials, level 3 won 12 and level 2 won 2.
Read that table by row rather than by total, because the rows say something the total hides. The
three circuits where the levels agree are all small. They agree because there was nothing to
disagree about: MinimumPoint cannot return a better circuit if the trajectory has no non-monotone
stretch, and VF2PostLayout cannot find a better qubit set if the circuit is small enough that every
candidate is equivalent. On the two larger circuits the levels differed on 14 of 16 seeds.
That section of Chapter 28 exists because its author first tested two small circuits, found them byte-identical, and wrote that Qiskit had unified the levels. Two examples agreed, so the conclusion was that they always would — the book's most-repeated error, arriving inside the transpiler.
And it is worth knowing why level 3's two losses happened, because they are not malfunctions:
QFT(8) seed 7 L2 depth 343 L3 depth 318 L2 ecr 94 L3 ecr 104
Level 3 returned a circuit that was 25 layers shallower and carried ten more two-qubit gates.
MinimumPoint's guarantee is over size and depth, which are the properties the convergence loop
tracks. It is not a guarantee about two-qubit count and it is certainly not one about fidelity. The
loop minimised what it was told to minimise; the scoring rule that called it a loss ranks two-qubit
count first. Two objectives disagreed, one level down inside the compiler.
This chapter's own anomalies are the same phenomenon. Level 2 produced a deeper circuit than level 1
on the efficient_su2 ansatz (85 versus 73), and Case Study 1 measured level 3 needing 83 two-qubit
gates against level 2's 80. Neither is a bug. Both are a pipeline optimising a proxy you did not
choose.
🔬 Honest Assessment — "Prefer level 3" is a weak preference, not a result.
Twelve wins against two looks decisive. Treat the 14 trials as independent coin flips and it is significant at $p = 0.0065$ one-sided.
They are not independent. All 14 differing trials come from exactly two circuits — QFT(8) contributed 6 and Grover-ish(7) contributed 8 — and both of level 3's losses sit in the same family, the QFT(8) rows. Score by circuit instead of by trial and level 3 wins 2–0, with $p = 0.5$. Two samples.
Which is precisely the sample size that produced the error the section was written to correct. A correction to a two-sample conclusion should not itself rest on two samples.
The claim that survives is the one that does not depend on the win rate at all: levels 2 and 3 are genuinely different pipelines, they diverge on 35% of circuit-seed pairs overall and on 14 of 16 pairs drawn from circuits large enough to have alternatives, and anything you concluded about them from a small circuit transfers to neither.
Practically: try level 3, keep it if it wins on your circuit, and do not report "we used level 3 because it is better" as though it were established.
Level 0 is the only level that keeps your gates
There is one job level 0 does that no other level can, and it has nothing to do with speed.
Level 0 is the only level that leaves your circuit alone. Every other level is entitled to delete any gate whose removal does not change the unitary — which is correct, documented, desirable behaviour, and catastrophic in the one case where a gate's presence carries meaning that its unitary does not.
Chapter 25 hit it. Its three-qubit repetition-code experiments mark the places where noise should be
injected with identity gates, because Aer attaches noise to gates, and the noise model binds an
$X$ error to id on each data qubit. An identity gate is by construction removable:
optimization_level=0: id gates surviving = 3 <- noise attaches here
optimization_level=1: id gates surviving = 0 <- NOISE SLOTS DELETED
optimization_level=2: id gates surviving = 0
optimization_level=3: id gates surviving = 0
Level 0 is not the default. transpile(qc, sim) therefore produced a circuit with nowhere for the
noise to go, and the simulator faithfully executed a noiseless one:
p unencoded level=0 default
0.10 0.1000 0.0283 0.0000
0.50 0.5000 0.5003 0.0000
0.60 0.6000 0.6494 0.0000
Perfect error correction at $p = 0.60$, where a three-qubit code cannot correct anything at all. The result was not merely wrong; it was wrong in the flattering direction, which is why nothing about it looked suspicious.
The general rule, and it is broader than error correction: if you are relying on a gate's presence rather than its action, level 0 is the only level that will honour it, and even then you should assert. The cases that come up in practice:
- Noise slots — Chapter 25's
idmarkers. - Timing markers — a
delayor abarrieryou inserted to control when something happens (Chapter 31). - Benchmark gate counts — a randomised-benchmarking sequence whose whole point is that it contains exactly $m$ Cliffords composing to the identity. An optimizer that notices they compose to the identity has destroyed the experiment (Chapter 30).
- Calibration circuits — anything measuring the error of a specific gate on a specific pair.
🐛 Debug This — "My noise model has no effect."
Symptom. You build a noise model, attach it to
AerSimulator, run, and the results are indistinguishable from noiseless — or improbably good, which is the more dangerous version because it looks like a finding.First check, and it takes one line. Count the ops on the transpiled circuit, not the one you built:
python t = transpile(qc, sim, optimization_level=0) assert t.count_ops().get("id", 0) == 3, f"noise slots optimized away: {dict(t.count_ops())}"Second check. Every gate your noise model mentions must appear in
t.count_ops()under exactly that name. A model that attaches errors tocxsees nothing on a circuit transpiled toecr. A model that attaches tou3sees nothing after basis translation torz/sx/x. Basis translation renames your gates, and a noise model keyed on the old names silently matches nothing.Why nothing warns you. There is no exception, no changed gate count in the object you built, no difference in the shape of the returned counts. The circuit you constructed and the circuit that ran are different objects, and the only place the difference is visible is
count_ops()on the transpiled one.The habit: assert on the transpiled circuit. It is not defensive programming — it is the experiment's control. Without it, a noise study has no evidence that it contained any noise.
10.6 Layout and Routing Methods
Beneath the levels are the specific algorithms, and you can select them directly.
Layout
for method in ("trivial", "dense", "sabre"):
pm = generate_preset_pass_manager(optimization_level=1, backend=backend,
layout_method=method, seed_transpiler=42)
isa = pm.run(qc)
trivial ecr 34 depth 103 layout [4, 3, 0, 2, 1]
dense ecr 34 depth 92 layout [59, 61, 41, 60, 53]
sabre ecr 28 depth 80 layout [85, 73, 68, 66, 67]
| Method | Strategy |
|---|---|
| trivial | logical $i$ → physical $i$. Fast, and blind to both connectivity and error rates |
| dense | find the densest connected subgraph of the right size |
| sabre | SWAP-based heuristic search; runs routing forward and backward to refine the initial layout |
| VF2 | exact subgraph isomorphism — used when a perfect layout exists, and it scores candidates by error rate |
SABRE wins here — 28 gates against 34, and a depth of 80 against 103. It wins by co-designing the layout with the routing rather than choosing the layout first.
How SABRE decides
SABRE is SWAP-based bidirectional heuristic search, and knowing its loop explains three things
this chapter measures: why the layout and routing methods share a name, why it beats basic by more
than 2×, and why §10.9's seed matters at all.
The loop, roughly:
- Take the front layer — the gates whose predecessors have all executed but which are not yet executable, because their two qubits are not adjacent on the coupling map.
- Enumerate every SWAP touching a qubit in that front layer. That is a small set: on a degree-3 lattice with a five-gate front layer it is a couple of dozen candidates.
- Score each candidate by how much it reduces the summed graph distance of the front layer, plus a lookahead term over the next few layers and a decay term that discourages reusing the same qubits over and over.
- Apply the best one. Repeat.
Then the part the name refers to. Reverse the circuit, route it backwards, and use the qubit mapping that falls out as the initial layout for the next forward pass. Routing a circuit tells you where its qubits wanted to be; running it in reverse converts that into a better place to start. Iterate a couple of times and the layout and the routing have been chosen together.
That is why layout_method="sabre" and routing_method="sabre" are the same algorithm. The
layout is a by-product of running the router. trivial and dense both commit to a layout while
knowing nothing about what routing will need — trivial knows nothing at all, dense knows only that
dense neighbourhoods tend to route better — and the 34-against-28 gap in the table above is the cost
of choosing blind.
Now apply §10.3's residual test to the routing table, since all three rows were transpiled at level 1 and should therefore be clean multiples of three:
method 2q gates residual swaps inserted
--------- -------- -------- --------------
basic 58 48 16
lookahead 31 21 7
sabre 28 18 6
They are. And the numbers say something the raw gate counts blur: basic did 2.7× the routing work
of SABRE, not 2.1×. The 2.1× ratio is diluted by the ten logical CNOTs that every method has to run
regardless. Strip those out and the algorithmic gap is wider than the headline.
The mechanism is SWAP reuse. basic routes one gate at a time along the shortest path for that
gate alone; a SWAP it inserts leaves the qubits somewhere useful for the next gate only by accident.
SABRE's score sums over every currently-blocked gate, so it systematically prefers SWAPs that unblock
several at once, and its decay term stops it from ping-ponging the same pair back and forth.
Two consequences worth carrying:
SABRE breaks ties at random, and on a symmetric lattice ties are everywhere. Several SWAPs frequently reduce the front-layer distance by the same amount, and the choice among them is a coin flip. That is the entire mechanism behind §10.9 — the seed does not perturb a deterministic search, it is the search's tie-breaker, and a different tie can send the rest of the routing down a different branch.
What SABRE cannot do is invent an edge. Hand it a five-qubit all-to-all circuit on a degree-3 lattice and every single one of its decisions is a decision about which SWAPs to pay for. None of them is a decision about whether to pay. That is §10.3's point restated from inside the compiler: structure beats settings, because settings only choose among the costs your structure has already committed you to.
🔀 In Another Framework — the coupling map lives in three different places.
Cirq (1.7.0) puts the topology in the qubit type. A
cirq.LineQubitknows it lies on a line and acirq.GridQubitknows its coordinates, so writing an all-to-all circuit on a grid device is something you have to do on purpose rather than by accident. Routing iscirq.RouteCQCand basis translation iscirq.optimize_for_target_gateset— two separate transformers you compose, where Qiskit hands you oneoptimization_levelinteger that bundles them. Chapter 14 covers the trade.PennyLane (0.45.1, Chapter 16) treats routing as a transform:
qml.transforms.transpile(tape, coupling_map, device=None). The coupling map is an explicit argument rather than a property of a backend object, which makes "what would this ansatz cost on a different topology?" a one-line question. Asking it in Qiskit means instantiating a second backend to transpile against.Q# (Chapter 15) has no coupling map in the programming model at all. You write against an abstract machine and its resource estimator prices a connectivity assumption rather than a specific chip. That is the right abstraction for the fault-tolerant regime, where the physical layout is hidden underneath the error-correcting code — and the wrong one for choosing an
entanglementstring today.The measurement is architecture-specific; the method is not. Count logical two-qubit gates, count hardware two-qubit gates, divide.
Routing
basic ecr 58 depth 148
lookahead ecr 31 depth 77
sabre ecr 28 depth 78
basic needs more than twice as many two-qubit gates as sabre. It inserts SWAPs greedily,
one gate at a time, with no lookahead. It exists as a baseline and as a fallback; it is not a serious
choice.
The gap between lookahead (31) and sabre (28) is much smaller, and either is defensible.
🔬 Honest Assessment — Routing is NP-hard, and that is why the choices matter.
Finding the minimum-SWAP routing for a circuit on a given topology is an NP-hard optimization problem. Every method above is a heuristic, and none of them is optimal.
Two consequences that matter practically.
The methods are genuinely different, not merely tuned differently. A 2× spread between
basicandsabreis not a rounding error; it reflects different algorithmic ideas.A better routing algorithm is a real research contribution, and the field produces them regularly. If your circuits are routing-limited — which you can check by comparing logical to transpiled two-qubit counts — it is worth reading the current literature rather than accepting the default.
It also means you should be sceptical of any resource estimate that quotes logical gate counts for a topology-mismatched circuit. Chapter 23's Shor estimate is careful about this, and many published estimates are not.
10.7 Reading the Diff
Chapter 6 §6.5 introduced the technique; here is the disciplined version.
import difflib
from qiskit import qasm3
def qasm_at(qc, level):
pm = generate_preset_pass_manager(optimization_level=level, backend=backend,
seed_transpiler=42)
return qasm3.dumps(pm.run(qc)).splitlines()
print("\n".join(difflib.unified_diff(qasm_at(qc, 0), qasm_at(qc, 3),
"level 0", "level 3", lineterm="")))
And the four numbers to extract from any transpiled circuit, from Chapter 6's key takeaways:
| Number | How | Tells you |
|---|---|---|
| physical qubits | isa.layout.final_index_layout() |
which layout — record this |
| two-qubit count | count ecr/cz/cx |
whether SWAPs were inserted (compare to logical) |
| real pulses | count sx + x |
true single-qubit cost |
| depth | isa.depth() |
coherence budget consumed |
The routing check is the important one. If the transpiled two-qubit count exceeds what you wrote, you are paying for routing, and layout or entanglement-pattern changes will help. If it matches, your circuit already fits the topology and further effort belongs elsewhere.
Reading final_index_layout()
The first row of that table is the one people record without understanding, so it is worth being exact.
final_index_layout() returns a list whose $i$-th element is the physical qubit that your
circuit's qubit $i$ ends up on. Not the qubit it started on — the qubit it ends on. Qiskit exposes
both, and the difference is the whole routing story:
isa.layout.initial_index_layout(filter_ancillas=True) # what LAYOUT chose
isa.layout.final_index_layout() # where ROUTING left them
Run both on §10.3's circuit at each level:
level | initial_index_layout | final_index_layout | 2q
------|------------------------|-----------------------|----
0 | [ 0, 1, 2, 3, 4] | [ 4, 3, 0, 2, 1] | 34
1 | [59, 60, 58, 61, 62] | [62, 61, 58, 60, 59] | 28
2 | [60, 59, 53, 61, 58] | [58, 53, 59, 61, 60] | 18
3 | [60, 59, 61, 53, 58] | [58, 61, 59, 53, 60] | 18
Level 0's layout stage chose the identity — logical $i$ to physical $i$, exactly as TrivialLayout
promises — and level 0's final layout is not the identity. Nothing chose [4, 3, 0, 2, 1]. That is
where eight SWAPs happened to leave the qubits. It is a consequence, not a decision, and reading it
as a decision is the most common misreading of the field's most-recorded number.
Three things follow, and all three are visible in the table.
In practice, routing permutes within the neighbourhood the layout stage chose rather than leaving
it. Every final row above is a permutation of its own initial row: level 1 uses
$\{58, 59, 60, 61, 62\}$ before and after; level 3 uses $\{53, 58, 59, 60, 61\}$ before and after.
Checking it across three circuits, four optimization levels and twelve seeds — 144 compilations —
the set was identical in all 144.
That is a measurement, not a guarantee. Nothing in the architecture forbids the router from swapping a data qubit onto an ancilla outside the chosen set, and SABRE only avoids it because its scoring function has no reason to walk away from a neighbourhood it selected for being good. Treat "the layout stage picks the neighbourhood, the routing stage shuffles within it" as a reliable expectation you should still verify on your own circuits, with one line comparing the two sets.
So compare layouts two different ways depending on the question. If you are asking which physical qubits bore the error — the thing Chapter 4's Case Study 1 measured an eightfold fidelity swing from — compare the sets, and a reordering is irrelevant. If you are asking which qubit produced which bit — how to interpret a result, or whether two runs are the same compilation — compare the lists, and a reordering is everything.
And levels 2 and 3 picked the same five qubits. Their initial layouts differ only by transposing two entries, their final layouts differ by more, and both produced exactly 18 two-qubit gates. A different assignment does not have to cost differently; it just usually does.
⚠️ Common Pitfall — Recording the layout you asked for instead of the layout you got.
If you pass
initial_layout=[10, 11, 12, 13, 14]and then record those five integers as your result's provenance, you have recorded an input, not an outcome. The transpiler honours the assignment, then routes on top of it, and the qubits your bits came off are the final ones.Worse,
initial_layoutdoes not prevent routing. It only removes the layout stage's freedom to pick a neighbourhood that routes well. Chapter 29 measured what that costs: a hand-picked chain scored 0.6790 where a calibration-picked layout scored 0.9764. Pinning a layout by hand is an assertion that you know the chip's error profile better than the transpiler does, on the day you ran it.Record
final_index_layout(), and record the calibration date next to it.
10.8 Writing a Custom Pass
Sometimes you need something the presets do not do — an analysis you want, or a transformation
specific to your circuits. A pass is a class with a run method over a DAG.
An analysis pass
from qiskit.transpiler import TransformationPass
from qiskit.dagcircuit import DAGCircuit
class CountTwoQubit(TransformationPass):
"""Count two-qubit gates and record the number in property_set."""
def run(self, dag: DAGCircuit) -> DAGCircuit:
n = sum(1 for node in dag.op_nodes() if len(node.qargs) == 2)
self.property_set["two_qubit_count"] = n
return dag
pm = PassManager([CountTwoQubit()])
pm.run(qc)
print(pm.property_set["two_qubit_count"]) # 10
property_set is the pass manager's shared scratchpad — how passes communicate. A layout pass writes
property_set["layout"]; routing reads it.
A transformation pass
class CancelAdjacentCX(TransformationPass):
"""Remove adjacent identical CNOT pairs (CX . CX = I)."""
def run(self, dag: DAGCircuit) -> DAGCircuit:
for run_ in dag.collect_runs(["cx"]):
i = 0
while i + 1 < len(run_):
a, b = run_[i], run_[i + 1]
if a.qargs == b.qargs:
dag.remove_op_node(a)
dag.remove_op_node(b)
i += 2
else:
i += 1
return dag
qc = QuantumCircuit(2)
qc.h(0); qc.cx(0, 1); qc.cx(0, 1); qc.h(0)
print(dict(qc.count_ops())) # {'h': 2, 'cx': 2}
print(dict(PassManager([CancelAdjacentCX()]).run(qc).count_ops())) # {'h': 2}
Both CNOTs gone, exactly as Chapter 2's Exercise 2.10 predicted.
dag.collect_runs(["cx"]) returns maximal sequences of consecutive cx nodes on the same
qubits — the DAG API doing the hard part. Chapter 28 builds several more useful passes on this
foundation.
⚠️ Common Pitfall — Do not hand-write optimizations the transpiler already does.
CancelAdjacentCXabove is a teaching example. Qiskit'sInverseCancellationandCommutativeCancellationpasses do this and considerably more, correctly, including cases your version misses (non-adjacent but commuting CNOTs, for one).Write a custom pass when you need something domain-specific that a general-purpose transpiler cannot know: a symmetry particular to your ansatz, a hardware constraint the target does not express, an instrumentation hook. Do not write one to redo work that is already done well.
10.9 The Seed
The finding that must change your practice.
for seed in (1, 42, 7, 123):
pm = generate_preset_pass_manager(optimization_level=3, backend=backend,
seed_transpiler=seed)
isa = pm.run(qc)
seed | 2q gates | depth | layout
--------------------------------------------------
1 | 20 | 74 | [58, 53, 61, 60, 59]
7 | 18 | 66 | [58, 61, 59, 53, 60]
42 | 18 | 69 | [58, 61, 59, 53, 60]
123 | 21 | 68 | [41, 53, 59, 61, 60]
2024 | 21 | 68 | [41, 53, 59, 61, 60]
31337 | 21 | 77 | [58, 59, 53, 61, 60]
Same circuit. Same device. Same optimization level. Two-qubit counts from 18 to 21 — a 17% spread — and four different layouts.
SABRE is a randomized heuristic. Without a fixed seed, every transpilation is a different sample from a distribution of results.
⚠️ Common Pitfall — An unseeded transpilation makes a result unreproducible.
This compounds with everything else. A hardware result depends on:
- the layout, which depends on the seed;
- the gate count, which depends on the layout;
- the fidelity, which depends on both — Chapter 4's Case Study 1 measured an eightfold swing from layout alone.
So a result reported without
seed_transpilercannot be reproduced even by the person who produced it, on the same machine, ten minutes later.Two habits, both free:
python pm = generate_preset_pass_manager(optimization_level=3, backend=backend, seed_transpiler=42) # ALWAYS print(isa.layout.final_index_layout()) # RECORD ITAnd a third, which is a genuine technique rather than hygiene: transpile several times with different seeds and keep the best.
The same five qubits, three different answers
Look at the layout column again, but as sets rather than as lists.
seed | 2q | depth | layout | physical qubit SET
------|----|-------|-------------------------|--------------------------
1 | 20 | 74 | [58, 53, 61, 60, 59] | {53, 58, 59, 60, 61}
7 | 18 | 66 | [58, 61, 59, 53, 60] | {53, 58, 59, 60, 61}
42 | 18 | 69 | [58, 61, 59, 53, 60] | {53, 58, 59, 60, 61}
123 | 21 | 68 | [41, 53, 59, 61, 60] | {41, 53, 59, 60, 61}
2024 | 21 | 68 | [41, 53, 59, 61, 60] | {41, 53, 59, 60, 61}
31337 | 21 | 77 | [58, 59, 53, 61, 60] | {53, 58, 59, 60, 61}
Four of the six seeds chose the identical five physical qubits — and got 18, 18, 20, and 21 two-qubit gates. The variation is not mostly about which qubits SABRE picked. It is about which logical qubit it put on which, and in what order it then routed them.
That matters because it separates two things people conflate. Layout quality and layout choice are
different axes. Chapter 4's eightfold fidelity swing came from landing on bad qubits; this spread
came from landing on the same qubits in a different arrangement. Both are seed-driven, both hurt,
and only one of them is fixed by pinning initial_layout.
Then look at seeds 7 and 42, which is the sharper observation:
7 | 18 gates | depth 66 | [58, 61, 59, 53, 60]
42 | 18 gates | depth 69 | [58, 61, 59, 53, 60]
Identical final layout. Identical two-qubit count. Depths differing by 5%. The two runs agreed on where every qubit ended up and on how many gates it took to get there, and still produced different circuits — because the order in which the SWAPs were applied differs, and order determines how much of the circuit can run in parallel.
So the layout is a summary, not a fingerprint. Two compilations can match on every field of the provenance record this chapter recommends and still not be the same circuit. Recording the seed is what closes that gap, which is the argument for recording it even when you have recorded everything else.
A fixed seed is not a fixed layout
Here is the limit of the advice above, and it is the most important caveat in the chapter.
seed_transpiler=42 guarantees a reproducible compilation only against a fixed backend
configuration. The layout stage does not choose a neighbourhood by connectivity alone — from level 1
upward it scores candidates using the device's measured error rates, and level 3 adds
VF2PostLayout, which re-scores the layout after routing on exactly that data. Those numbers are
refreshed every time the device is recalibrated.
When the calibration changes, the cost function changes, so the same seed minimises a different
function and lands somewhere else. Chapter 39's Case Study 39.2 is a group that discovered this the
expensive way: they had pinned their seed, they had pinned requirements.txt, and their result
still failed to reproduce three months later.
The pinned environment and the fixed seed pin everything on the client side, which is the side that was never the problem.
Their reported 34% improvement came back as 11% and 7%. The joint correction the two groups eventually published was "18% on average (range 7–34%) across 12 executions spanning three weeks" — a weaker headline and a claim that survives contact with a second group.
The scale of what a seed can move, from the same chapter's sweep of a 14-qubit efficient_su2 across
24 seeds:
worst fidelity 0.5755 seed 20 109 2q gates depth 247
median fidelity 0.6396
best fidelity 0.7911 seed 23 49 2q gates depth 149
error ratio worst/best: 2.03x 2q gate count range: 49 - 112
Twice the error, from nothing but the seed. Apply §10.3's residual test — circular entanglement
on 14 qubits at reps=2 is one CX per qubit per repetition, so $14 \times 2 = 28$ logical two-qubit
gates:
outcome 2q gates minus 28 logical swaps inserted
best (seed 23) 49 21 7
worst gate count 112 84 28
Both residuals divide by three exactly, so this is routing and nothing else. The router inserted seven SWAPs on the lucky seed and twenty-eight on the unlucky one, for the same 28-gate circuit. At 112 gates, 75% of what executes on the device is the device's fault rather than the algorithm's.
And now the caution that keeps this from being over-read. Chapter 39 ran the identical sweep on a 4-qubit circuit and found exactly zero variation across all 24 seeds, concluding that layout variance is a large-circuit phenomenon.
Chapter 29 §29.5 found the confound. The 14-qubit circuit was circular — a ring, which cannot
embed in a girth-12 lattice — and the 4-qubit control was a plain cx chain, which embeds
perfectly. The two experiments differ in width and in shape, so attributing the difference to
width is unsupported. Holding width fixed at six qubits and varying only the shape, Chapter 29
measured the hardware-aware circuit compiling to one layout across 24 seeds, with identical gate
count and identical depth, while the naive circuit spanned 135 to 165 two-qubit gates and depths of
310 to 408 from nothing but the seed.
So the rule is about shape, not size: whether the seed matters at all is a property of your
circuit's interaction graph. A circuit whose graph embeds in the coupling map gives SABRE nothing to
be random about — VF2Layout finds a perfect embedding, the router has no SWAPs to choose between,
and the heuristic search never runs. This chapter's own linear chain is the demonstration: four gates,
at every optimization level, and by the same argument at every seed.
Which turns the seed sweep into a diagnostic as well as an optimization. Run it, and if you see no spread, you have learned that your circuit fits the device — which is the better news of the two.
💰 Cost and Queue — the compilation is free; its consequences are billed.
Everything in this section runs on your laptop. Four seeds cost 31 extra milliseconds of local CPU and nothing else — no queue, no shots, no credits.
The unlucky seed is not free. Take the two ends of the 14-qubit sweep and price them under the two billing models Chapter 39 compares:
text best seed 23 worst seed 20 ratio two-qubit gates 49 109 2.22x depth 149 247 1.66x fidelity 0.7911 0.5755 error 0.2089 0.4245 2.03xUnder a per-minute (QPU-time) model, execution time tracks circuit duration, so the unlucky seed would bill roughly 1.66× as much — and return 2.03× the error. Paying more for less.
Under a per-shot model the two compilations bill identically, because the price does not know how deep your circuit is. Same invoice, twice the error, and nothing on the invoice records which one you got. Chapter 39 measured the two models diverging by 149× on the same VQE run ($50 per-minute against $7,432 per-shot), so which of these paragraphs applies to you is worth knowing before you submit.
Either way the fix costs 31 milliseconds of a machine you already own. This is the most favourable cost-benefit ratio in the book, and §10.9's real puzzle is why it is not the default.
Best-of-N: the cheapest optimization available
If the seed changes the answer, sample it. Score each result by two-qubit count, breaking ties on depth:
def best_of(circuit, n_seeds, level=3):
best = None
for seed in range(n_seeds):
pm = generate_preset_pass_manager(optimization_level=level,
backend=backend, seed_transpiler=seed)
isa = pm.run(circuit)
key = (two_qubit_count(isa), isa.depth())
if best is None or key < best[0]:
best = (key, seed, isa)
return best
n_seeds | best 2q | depth | seed | time
-------------------------------------------------
1 | 20 | 74 | 0 | 10 ms
4 | 18 | 66 | 2 | 41 ms
16 | 18 | 66 | 2 | 148 ms
32 | 18 | 66 | 2 | 287 ms
Four seeds bought a 10% reduction in two-qubit gates and 11% in depth, for 31 extra milliseconds. Beyond four, no further gain on this circuit — which is itself useful to know, and is the kind of thing you measure rather than guess.
Thirty milliseconds against a circuit you will then run thousands of times. This is the cheapest optimization in the book, and almost nobody does it.
🧱 Project Checkpoint —
backends.pyv2: transpile once, keep the best.Two additions, both consequences of this chapter.
prepare()gains an_seedsparameter. It transpiles the ansatz with several seeds, scores each by two-qubit gate count (breaking ties on depth), and returns the best. Measured on the project's ansatz this is a few percent, and on a routing-limited circuit it is more; it costs milliseconds either way.The result records its provenance.
Preparedgainsseed,optimization_level, andtwo_qubit_countfields, so that every energy the project ever reports can be traced to the exact compilation that produced it.That second one is the point. Chapter 7's checkpoint made the layout trap unrepresentable; this one makes the compilation reproducible. Between them, a
vqelabresult can be regenerated exactly — which is the minimum bar for a number you intend to defend.
10.10 Transpile Once, Bind Many — Revisited
Chapter 7 §7.7 established the pattern and measured a 31× speedup at 100 iterations. This chapter explains why the saving is so large: transpilation is not one operation but a pipeline of graph algorithms, including an NP-hard routing problem attacked by randomized search.
efficient_su2(12, reps=3):
level 0: 1.5 ms
level 1: 4.1 ms
level 2: 4.0 ms
level 3: 7.7 ms
Eight milliseconds is nothing once. Inside a 200-iteration optimizer loop it is 1.6 seconds of pure waste, and on a larger circuit where transpilation takes a second it is five minutes.
Transpile the parameterized circuit once. Bind values afterwards. And now you can add: transpile it once with several seeds, keep the best, and reuse that for the whole run.
10.11 Summary
The transpiler solves four hardware-imposed problems: your gates do not exist, your qubits are abstract, your qubits are not all connected, and your circuit is longer than it needs to be.
A current IBM device offers {ecr, rz, sx, x} plus measure, reset, delay, and control flow — and
127 qubits with 72 connections, which is 0.90% of all-to-all. The heavy-hex sparsity is
deliberate: fewer couplings means less crosstalk and better coherence. The routing overhead is the
price of the low error rates.
Routing is the cost that surprises people. A five-qubit all-to-all circuit with ten logical CNOTs transpiled to 34 two-qubit gates at optimization level 0 and 18 at level 2. Each SWAP costs three CNOTs, and there is no cheaper construction. Your gate budget is in transpiled gates.
But the overhead is not a constant tax — it depends entirely on the mismatch. The same experiment
on a linear chain gave 4 physical gates for 4 logical CNOTs, at every optimization level and
every width: a ratio of exactly 1.0, while all-to-all ran 1.8–2.2× and widening. Chapter 8's
linear and pairwise entanglement patterns are the difference between paying nothing and paying
triple.
The pass manager has six stages — init, layout, routing, translation, optimization, scheduling — and the optimization stage iterates to a fixed point, which is where the higher levels spend their time.
Optimization levels differ in which algorithms they use, not merely in effort. When routing is needed the level matters enormously (34 vs 18); when it is not, it barely affects two-qubit count and level 2 can produce a deeper circuit than level 1 — measured. Level 0 for exact control, level 1 for iteration, level 2 as the working default, level 3 when measurement says it wins.
Layout and routing methods can be chosen directly. SABRE beat trivial and dense layout (28 vs 34
gates), and basic routing needed more than twice as many two-qubit gates as SABRE. Routing is
NP-hard; all of these are heuristics, and the differences are algorithmic rather than cosmetic.
Custom passes are classes with a run(dag) method, communicating through property_set. Write
them for domain-specific knowledge the transpiler cannot have — not to redo optimizations it already
performs well.
⚠️ Record seed_transpiler. SABRE is randomized: six seeds gave two-qubit counts from 18 to 21 —
a 17% spread — and four different layouts, on the same circuit. Layout depends on the seed, gate count
on the layout, and fidelity on both, so an unseeded result is unreproducible by its own author.
Better still: transpile with several seeds and keep the best. Four seeds bought 10% fewer two-qubit gates and 11% less depth for 31 extra milliseconds — the cheapest optimization in this book, against a circuit you will then run thousands of times.
Next: Chapter 11 — the simulator family in depth. Statevector, density matrix, stabilizer, matrix product states, and how to build a noise model that reproduces a real device on your laptop.