Layout Algorithms
What a Layout Algorithm Does
A layout algorithm decides which physical qubit on the hardware each logical qubit in your circuit will run on. Given a circuit (in logical qubit space) and a coupling map describing the hardware's connectivity, it returns a dict mapping logical qubit indices to physical qubit indices. That mapping is stored on the circuit and used by every subsequent pass.
Tessera ships three built-in algorithms, each registered under a string key, and also accepts custom callables. Select one with the layout_algorithm parameter on transpile():
# Pass a registry key
transpile(qc, backend="IBM", layout_algorithm="dense")
transpile(qc, backend="IBM", layout_algorithm="sabre")
transpile(qc, backend="IBM", layout_algorithm="trivial")
# Or pass a custom callable
def my_layout(circuit, coupling_map):
return {i: i for i in range(circuit.num_qubits)}
transpile(qc, backend="IBM", layout_algorithm=my_layout)Choosing an Algorithm
| Key | Approach | When to use |
|---|---|---|
"dense" (default) | Greedy by interaction frequency | Fast, produces good layouts on most circuits |
"sabre" | Forward-backward trial routing | Higher quality on circuits where routing cost matters; slower |
"trivial" | Direct logical to physical mapping | Debugging, baselines, all-to-all topologies |
Dense
Key: "dense" (default)
Counts how often each pair of logical qubits interacts in the circuit and greedily assigns the most-frequently-interacting pairs to closely-connected physical qubits. The default since v1.0. Designed to minimize the work the router has to do later.
How it works:
- Count interactions for every logical qubit pair across all two-qubit gates
- Sort pairs by interaction count, highest first
- For each pair, place it on the closest available physical qubits (if one qubit is already placed, pick the closest free neighbor; otherwise pick the closest free pair on the device)
- Assign any remaining unplaced logical qubits to leftover physical positions
transpile(qc, backend="IBM", layout_algorithm="dense")SABRE
Key: "sabre"
Initial mapping discovery using forward-backward trial routing, originally described in Li, Ding, Xie 2019. Starts from a trivial mapping, runs the SABRE routing algorithm forward across the circuit, then backward across the reversed circuit, and keeps the final mapping each pass produces. Repeated a few times to converge on a layout that minimizes the trial routing's SWAP count.
Often produces higher-quality layouts than dense on circuits where routing cost dominates, at the cost of doing the trial routing twice per iteration. The trial always uses the SABRE routing algorithm internally even if you pick a different routing algorithm for the actual routing pass.
transpile(qc, backend="IBM", layout_algorithm="sabre")Trivial
Key: "trivial"
Maps logical qubit i directly to physical qubit i, one-to-one. Performs no optimization. Useful as a baseline for benchmarking other layouts, when debugging routing issues without layout effects in the picture, or when running on all-to-all topologies (like IonQ) where the layout choice doesn't matter.
transpile(qc, backend="IBM", layout_algorithm="trivial")Custom Layout Algorithms
Pass a callable to layout_algorithm instead of a registered key. The callable must accept a TesseraCircuit and a TesseraCouplingMap and return a dict mapping logical qubit indices to physical qubit indices:
def my_layout(circuit, coupling_map) -> dict[int, int]:
# Inspect circuit.instructions, count interactions, do whatever you like.
# Return a dict mapping logical qubit indices to physical qubit indices.
return {0: 3, 1: 5, 2: 6}
transpile(qc, backend="IBM", layout_algorithm=my_layout)The returned dict can be sparse: not every logical qubit needs an entry if your algorithm only places the ones it cares about. The router fills in the gaps and routes through unmapped physical qubits when needed.
For details on how the layout pass wraps the algorithm and what it does with the returned mapping, see the Passes Reference.