transpilation.md
333 lines · skills/qiskit/references/transpilation.md at 5f2bc03
Target-Aware Transpilation
Transpilation rewrites an abstract circuit into an instruction set architecture (ISA) circuit that satisfies a specific backend Target:
- only supported instructions,
- valid qubit operands and connectivity,
- backend timing and control-flow constraints,
- an explicit virtual-to-physical qubit layout.
IBM Runtime V2 primitives require ISA circuits. They do not perform layout, routing, and basis translation automatically.
Raw lines 1 to 11Recommended Entry Point
Use a preset staged pass manager:
from qiskit.transpiler import generate_preset_pass_manager
pass_manager = generate_preset_pass_manager(
backend=backend,
optimization_level=1,
seed_transpiler=17,
)
isa_circuit = pass_manager.run(circuit)The pass manager can be reused for circuits targeting the same backend snapshot:
isa_circuits = pass_manager.run(circuits)qiskit.transpile() remains a convenient wrapper, but a pass manager is easier to reuse, inspect, and customize.
The BackendV2 Target
backend.target is the source of supported operations and constraints:
print("backend:", backend.name)
print("qubits:", backend.num_qubits)
print("operations:", sorted(backend.operation_names))
print("coupling map:", backend.coupling_map)
print("target:", backend.target)Do not use these removed or legacy BackendV1 access patterns:
# Do not use in Qiskit 2.x code:
# backend.configuration().basis_gates
# backend.properties()
# BackendPropertiesFor a synthetic target:
from qiskit.transpiler import CouplingMap, Target
target = Target.from_configuration(
basis_gates=["cz", "sx", "rz"],
coupling_map=CouplingMap.from_line(5),
)
pass_manager = generate_preset_pass_manager(
target=target,
optimization_level=1,
seed_transpiler=17,
)
isa_circuit = pass_manager.run(circuit)Prefer one coherent backend or target. Combining a backend with separate basis_gates or coupling_map inputs creates competing sources of truth and is discouraged.
Preset Optimization Levels
| Level | Typical intent |
|---|---|
| 0 | Minimal transformation needed to satisfy the target |
| 1 | Light optimization with relatively low compilation cost |
| 2 | More optimization and search |
| 3 | Heavier optimization and search; highest classical cost |
Higher does not guarantee a better experimental result. Compare levels using the same circuit, target snapshot, and seed:
compiled = {}
for level in (0, 1, 2, 3):
manager = generate_preset_pass_manager(
backend=backend,
optimization_level=level,
seed_transpiler=17,
)
compiled[level] = manager.run(circuit)Start with levels 1 and 3 for a bounded comparison. Evaluate target-native two-qubit operations, depth, estimated duration when available, and downstream result quality.
Raw lines 75 to 98Transpiler Stages
A preset StagedPassManager has six conceptual stages:
- init: validate and synthesize high-level operations.
- layout: map virtual qubits to physical qubits.
- routing: add routing operations to satisfy connectivity.
- translation: convert to target-supported instructions.
- optimization: simplify and resynthesize the target circuit.
- scheduling: apply hardware-aware timing passes when configured.
Qiskit 2.x scheduling does not restore qiskit.pulse; that module was removed.
Inspect the generated manager:
print(pass_manager)
print(pass_manager.property_set)The property set is primarily a pass-development and debugging interface; do not build long-term application logic around undocumented keys.
Raw lines 99 to 120Layouts and Observables
Transpilation can permute logical qubits. Estimator observables must be mapped to the final physical layout:
from qiskit.quantum_info import SparsePauliOp
observable = SparsePauliOp.from_list([("ZZ", 1.0)])
isa_circuit = pass_manager.run(circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)Submit the mapped observable:
pub = (isa_circuit, isa_observable, parameter_values)Do not manually guess the permutation from a circuit drawing. Use isa_circuit.layout.
For a list of observables:
isa_observables = [
observable.apply_layout(isa_circuit.layout)
for observable in observables
]Raw lines 121 to 149Parameterized Workloads
Transpile the parameterized circuit once:
parameterized_isa = pass_manager.run(parameterized_circuit)
isa_observable = observable.apply_layout(parameterized_isa.layout)
for values in optimizer_values:
pub = (parameterized_isa, isa_observable, [values])
result = estimator.run([pub], precision=0.03).result()[0]This avoids repeated layout and routing. Retranspile only when the circuit structure, target, selected backend feature set, or compilation strategy changes.
Raw lines 150 to 164Initial Layouts
Use an explicit layout only when calibration analysis or a reproducibility requirement justifies it:
pass_manager = generate_preset_pass_manager(
backend=backend,
optimization_level=2,
initial_layout=[3, 5, 8],
seed_transpiler=17,
)An explicit layout bypasses the preset layout-selection logic. Confirm every physical qubit and interaction is supported by the target.
Raw lines 165 to 179Approximate Synthesis
approximation_degree trades unitary fidelity for potentially cheaper circuits:
pass_manager = generate_preset_pass_manager(
backend=backend,
optimization_level=3,
approximation_degree=0.99,
seed_transpiler=17,
)Treat approximation as an experimental parameter. Compare ideal-unitary error, native two-qubit count, and hardware outcome. Do not describe 0.99 as a universal one-percent error bound for the whole algorithm.
Reproducibility
Set the transpiler seed explicitly:
pass_manager = generate_preset_pass_manager(
backend=backend,
optimization_level=3,
seed_transpiler=17,
)Qiskit 2.5 also supports QISKIT_TRANSPILER_SEED, but an explicit argument is clearer in reproducible code.
Record:
- Qiskit version,
- backend name and target/calibration timestamp if available,
- optimization level,
- seed,
- all non-default pass-manager arguments,
- input and output QPY artifacts,
- output layout and structural metrics.
A fixed seed stabilizes stochastic compiler choices; it does not freeze changing backend calibration data or package behavior across releases.
Raw lines 195 to 220Analyze Compiled Circuits
def two_qubit_instruction_count(circuit):
return sum(
len(item.qubits) == 2
for item in circuit.data
)
print("logical depth:", circuit.depth())
print("ISA depth:", isa_circuit.depth())
print("ISA size:", isa_circuit.size())
print("ISA operations:", isa_circuit.count_ops())
print("two-qubit instructions:", two_qubit_instruction_count(isa_circuit))
print("layout:", isa_circuit.layout)Count all two-qubit instructions rather than only cx; current targets may use cz, ecr, rzz, or other operations.
Circuit metrics are proxies. Calibration-aware quality depends on which physical qubits and operations were selected.
Raw lines 221 to 241Verify ISA Compatibility
Runtime rejects circuits that do not satisfy the target. A practical preflight is:
- Compile using the exact backend.
- Confirm the compiled width fits
backend.num_qubits. - Inspect every instruction name and qubit tuple against
backend.target. - Submit only the pass-manager output.
For advanced validation, use target APIs rather than a hardcoded basis list.
Raw lines 242 to 252Fake Backends and Aer
Test target-aware compilation without consuming QPU time:
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime.fake_provider import FakeSherbrooke
fake_backend = FakeSherbrooke()
pass_manager = generate_preset_pass_manager(
backend=fake_backend,
optimization_level=1,
seed_transpiler=17,
)
isa_circuit = pass_manager.run(circuit)Fake-backend availability can change between Runtime releases. List the installed fake-provider exports rather than assuming an old tutorial backend exists.
For an Aer noise model derived from a real backend:
from qiskit_aer import AerSimulator
noisy_simulator = AerSimulator.from_backend(backend)
simulator_pass_manager = generate_preset_pass_manager(
backend=noisy_simulator,
optimization_level=1,
seed_transpiler=17,
)
simulator_circuit = simulator_pass_manager.run(circuit)An Aer model is an approximation of the calibration data used to create it; it is not a faithful predictor of every QPU effect.
Raw lines 253 to 287Dynamic Circuits and Fractional Gates
Backend capabilities can depend on how the backend is requested:
backend = service.backend(
backend_name,
use_fractional_gates=True,
)Fractional-gate support and dynamic-control support have evolved across Runtime versions. The actual service keyword is use_fractional_gates=True; the fractional-gates guide currently contains a fractional_gates=True typo. Parameterized rzz angles must satisfy the returned target bounds (currently [0, pi/2] in the Runtime validator); parameter values outside the range are not repaired by compiling the symbolic circuit once. Inspect the returned target and current feature-compatibility documentation instead of assuming both are available for every backend and option combination.
If a circuit uses if_else, while_loop, switch_case, for_loop, or classical expressions:
- confirm the operations are present in
backend.operation_names, - compile against that returned backend object,
- check mitigation and fractional-gate compatibility,
- test the full branch behavior.
Custom Pass Managers
Customize only after measuring a limitation in preset output:
from qiskit.transpiler import PassManager
from qiskit.transpiler.passes import RemoveBarriers
cleanup = PassManager([RemoveBarriers()])
cleaned = cleanup.run(circuit)For target-aware custom pipelines, use StagedPassManager or modify a generated preset stage. Every custom transformation must preserve circuit semantics, parameters, control flow, and global phase as appropriate.
Write regression tests based on operator/state equivalence for small circuits and application-level outputs for larger circuits.
Raw lines 308 to 323Common Failures
- Circuit not ISA-compatible: submit the pass-manager output, not the abstract input.
- Wrong expectation value: map the observable through
isa_circuit.layout. - Too many routing operations: compare layout methods, seeds, initial layouts, and circuit connectivity.
- Compilation is slow: lower the optimization level or reduce repeated transpilation.
- BackendV1 attribute error: replace
configuration()andproperties()access with BackendV2 target and direct attributes. - Unknown instruction: inspect the exact backend target; do not use a generic basis copied from another device.
- Dynamic-circuit rejection: verify target control-flow operations and incompatible backend feature flags.
- Non-reproducible comparison: fix the seed and backend snapshot, and store all compiler settings.