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 full entanglement 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 v2prepare_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.