Chapter 10 — Key Takeaways (Transpilation)
The compiler page. Routing and seeds are the two things that change practice.
The four problems
| # | Problem | Solved by |
|---|---|---|
| 1 | your gates do not exist | basis translation |
| 2 | your qubits are abstract | layout |
| 3 | your qubits are not all connected | routing (SWAPs) |
| 4 | your circuit is longer than needed | optimization |
The device
target = backend.target
sorted(target.operation_names) # ['delay','ecr','for_loop','id','if_else',
# 'measure','reset','rz','switch_case','sx','x']
target.build_coupling_map() # 144 directed edges = 72 connections
127 qubits, 72 connections — 0.90% of all-to-all. No h, no cx, no ry. Four real gates.
The heavy-hex sparsity is deliberate: every coupling is an always-on element and a crosstalk channel. Routing overhead is the price of low gate error.
| Op | Real cost |
|---|---|
rz |
free — virtual |
sx, x |
1 pulse |
| any single-qubit gate | 3 rz + 2 sx = 2 pulses, always |
ecr |
1 two-qubit op, ~100× a single-qubit error |
★★ Routing: the cost that surprises
A SWAP costs three CNOTs. No cheaper construction.
5-qubit all-to-all, 10 logical CNOTs:
| level | depth | 2q gates | vs logical |
|---|---|---|---|
| 0 | 162 | 34 | 3.4× |
| 1 | 78 | 28 | 2.8× |
| 2 | 67 | 18 | 1.8× |
| 3 | 69 | 18 | 1.8× |
5-qubit linear chain, 4 logical CNOTs: 4 gates at every level — 1.0×.
And it widens with size:
| n | full logical → transpiled | ratio | linear | ratio |
|---|---|---|---|---|
| 4 | 12 → 21 | 1.75× | 6 → 6 | 1.00× |
| 6 | 30 → 80 | 2.67× | 10 → 10 | 1.00× |
| 8 | 56 → 156 | 2.79× | 14 → 14 | 1.00× |
| 10 | 90 → 284 | 3.16× | 18 → 18 | 1.00× |
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.
⚠️ A logical gate count cannot rank patterns. circular looks cheap logically (12 vs full's 30)
and has the worst routing ratio of all four (3.33×), because its one wrap-around link walks a
qubit the length of the chain.
Your gate budget is in transpiled gates.
The pass manager
| Stage | Decides |
|---|---|
| init | normalize, expand, drop useless gates |
| layout | which physical qubit each logical qubit maps to |
| routing | where to insert SWAPs |
| translation | rewrite into basis gates |
| optimization | cancel/merge/resynthesize — iterates to a fixed point |
| scheduling | delays and timing (Ch. 29) |
FixedPoint/DoWhileController in optimization = why higher levels cost time.
ConditionalController everywhere = most passes run only if needed.
Optimization levels
They differ in which algorithms they use, not merely in effort.
| Circuit type | Level matters? |
|---|---|
| routing-limited | enormously (34 → 18 gates) |
| topology-matched | barely — 2q identical at every level, only depth moves |
⚠️ Higher is not monotonically better. Measured: level 2 depth 85 vs level 1's 73 on one circuit; level 3 at 83 two-qubit gates vs level 2's 80 on another.
| Level | Use for |
|---|---|
| 0 | exact control — calibration, benchmarks where the transpiler must not help |
| 1 | fast iteration. Not for a reported result (Ch. 4 CS1's dead qubit) |
| 2 | the working default |
| 3 | try it, measure it, use it if it wins |
Layout and routing methods
generate_preset_pass_manager(..., layout_method="sabre", routing_method="sabre")
| Layout | 2q | depth |
|---|---|---|
| trivial | 34 | 103 |
| dense | 34 | 92 |
| sabre | 28 | 80 |
| Routing | 2q | depth |
|---|---|---|
| basic | 58 | 148 |
| lookahead | 31 | 77 |
| sabre | 28 | 78 |
basic needs 2.1× as many gates as sabre. Routing is NP-hard — all of these are heuristics,
none optimal, and the differences are algorithmic.
Custom passes
class MyPass(TransformationPass):
def run(self, dag: DAGCircuit) -> DAGCircuit:
self.property_set["my_metric"] = ... # passes communicate here
return dag
PassManager([MyPass()]).run(qc)
dag.collect_runs(["cx"]) gives maximal consecutive runs on the same qubits.
Write one for domain-specific knowledge the transpiler cannot have. Not to redo optimizations it
already does well (InverseCancellation, CommutativeCancellation).
★★ The seed
seed | 2q | depth | layout
2 | 18 | 66 | [58, 61, 59, 53, 60]
7 | 18 | 66 | [58, 61, 59, 53, 60]
0 | 20 | 74 | [58, 53, 61, 60, 59]
4 | 21 | 77 | [58, 59, 53, 61, 60]
18 to 21 gates — a 17% spread — and three layouts. SABRE is randomized.
It propagates and amplifies: seed → layout → gate count and error rate → fidelity. Chapter 4 measured an 8× fidelity swing from layout alone.
⚠️ Repeating is not reproducing. Repetition varies shots; most workflows transpile once, so the compilation variation stays hidden until someone else runs your code.
Two lines, free:
pm = generate_preset_pass_manager(..., seed_transpiler=42) # PIN IT
print(isa.layout.final_index_layout()) # RECORD IT
Best-of-N — the cheapest optimization in the book:
| n_seeds | best 2q | depth | time |
|---|---|---|---|
| 1 | 20 | 74 | 8 ms |
| 4 | 18 | 66 | 43 ms |
| 16 | 18 | 66 | 147 ms |
10% fewer gates and 11% less depth for 35 ms — against a circuit you will run thousands of times. And it makes the result deterministic given N.
Provenance to record with every hardware result
versions · backend name · seed_transpiler · optimization_level · physical layout ·
transpiled 2q count · shots (+ seed_simulator) · calibration window
Common pitfalls
- Budgeting in written gates rather than transpiled gates.
- Using
fullentanglement on a sparse topology. - Ranking entanglement patterns by logical gate count.
- Assuming level 3 beats level 2.
- Omitting
seed_transpiler. - Concluding stability from repeated runs in one session.
- Re-transpiling inside an optimizer loop (Ch. 7 §7.7).
Project piece added this chapter
vqelab/backends.py v2 — prepare_best() (transpile with N seeds, keep the best by two-qubit
count then depth), seed_sweep(), two_qubit_count(), and a frozen Compilation record carrying
seed, optimization level, layout, gate count, and depth.
Chapter 7 made the layout trap unrepresentable. This makes the compilation reproducible. Between them, every energy the project reports can be regenerated exactly — the minimum bar for a number you intend to defend.