Skip to main contentIBM Quantum Documentation Preview
This is a preview build of IBM Quantumâ„¢ documentation. Refer to docs.quantum.ibm.com for the official documentation.

Set transpiler optimization level

Package versions

The code on this page was developed using the following requirements. We recommend using these versions or newer.

qiskit[all]~=1.2.4
qiskit-ibm-runtime~=0.33.2
qiskit-aer~=0.15.1
qiskit-serverless~=0.18.0
qiskit-ibm-catalog~=0.2
qiskit-addon-sqd~=0.8.0
qiskit-addon-utils~=0.1.0
qiskit-addon-aqc-tensor~=0.1.2
qiskit-addon-obp~=0.1.0
scipy~=1.14.1
pyscf~=2.7.0

Real quantum devices are subject to noise and gate errors, so optimizing the circuits to reduce their depth and gate count can significantly improve the results obtained from executing those circuits. The generate_preset_pass_manager function has one required positional argument, optimization_level, that controls how much effort the transpiler spends on optimizing circuits. This argument can be an integer taking one of the values 0, 1, 2, or 3. Higher optimization levels generate more optimized circuits at the expense of longer compile times. The following table explains the optimizations performed with each setting.

Optimization LevelDescription
0

No optimization: typically used for hardware characterization

  • Basic translation
  • Layout/Routing: TrivialLayout, where it selects the same physical qubit numbers as virtual and inserts SWAPs to make it work (using StochasticSwap)
1

Light optimization:

  • Layout/Routing: Layout is first attempted with TrivialLayout. If additional SWAPs are required, a layout with a minimum number of SWAPs is found by using SabreSWAP, then it uses VF2LayoutPostLayout to try to select the best qubits in the graph.
  • InverseCancellation
  • 1Q gate optimization
2

Medium optimization:

  • Layout/Routing: Optimization level 1 (without trivial) + heuristic optimized with greater search depth and trials of optimization function. Because TrivialLayout is not used, there is no attempt to use the same physical and virtual qubit numbers.
  • CommutativeCancellation
3

High Optimization:

  • Optimization level 2 + heuristic optimized on layout/routing further with greater effort/trials
  • Resynthesis of two-qubit blocks using Cartan's KAK Decomposition.
  • Unitarity-breaking passes:
    • OptimizeSwapBeforeMeasure: Moves the measurements around to avoid SWAPs
    • RemoveDiagonalGatesBeforeMeasure: Removes gates before measurements that would not effect the measurements

Optimization level in action

Since two-qubit gates are typically the most significant source of errors, we can approximately quantify the transpilation's "hardware efficiency" by counting the number of two-qubit gates in the resulting circuit. Here, we'll try the different optimization levels on an input circuit consisting of a random unitary followed by a SWAP gate.

from qiskit import QuantumCircuit
from qiskit.circuit.library import UnitaryGate
from qiskit.quantum_info import Operator, random_unitary
 
UU = random_unitary(4, seed=12345)
rand_U = UnitaryGate(UU)
 
qc = QuantumCircuit(2)
qc.append(rand_U, range(2))
qc.swap(0, 1)
qc.draw("mpl", style="iqp")

Output:

We'll use the FakeSherbrooke mock backend in our examples. First, let's transpile using optimization level 0.

from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime.fake_provider import FakeSherbrooke
 
backend = FakeSherbrooke()
 
pass_manager = generate_preset_pass_manager(
    optimization_level=0, backend=backend, seed_transpiler=12345
)
qc_t1_exact = pass_manager.run(qc)
qc_t1_exact.draw("mpl", idle_wires=False)

Output:

The transpiled circuit has six of the two-qubit ECR gates.

Repeat for optimization level 1:

from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime.fake_provider import FakeSherbrooke
 
backend = FakeSherbrooke()
 
pass_manager = generate_preset_pass_manager(
    optimization_level=1, backend=backend, seed_transpiler=12345
)
qc_t1_exact = pass_manager.run(qc)
qc_t1_exact.draw("mpl", idle_wires=False)

Output:

The transpiled circuit still has six ECR gates, but the number of single-qubit gates has reduced.

Repeat for optimization level 2:

pass_manager = generate_preset_pass_manager(
    optimization_level=2, backend=backend, seed_transpiler=12345
)
qc_t2_exact = pass_manager.run(qc)
qc_t2_exact.draw("mpl", idle_wires=False)

Output:

This yields the same results as optimization level 1. Note that increasing the level of optimization does not always make a difference.

Repeat again, with optimization level 3:

pass_manager = generate_preset_pass_manager(
    optimization_level=3, backend=backend, seed_transpiler=12345
)
qc_t3_exact = pass_manager.run(qc)
qc_t3_exact.draw("mpl", idle_wires=False)

Output:

Now, there are only three ECR gates. We obtain this result because at optimization level 3, Qiskit tries to re-synthesize two-qubit blocks of gates, and any two-qubit gate can be implemented using at most three ECR gates. We can get even fewer ECR gates if we set approximation_degree to a value less than 1, allowing the transpiler to make approximations that may introduce some error in the gate decomposition (see Commonly used parameters for transpilation):

pass_manager = generate_preset_pass_manager(
    optimization_level=3,
    approximation_degree=0.99,
    backend=backend,
    seed_transpiler=12345,
)
qc_t3_approx = pass_manager.run(qc)
qc_t3_approx.draw("mpl", idle_wires=False)

Output:

This circuit has only two ECR gates, but it's an approximate circuit. To understand how its effect differs from the exact circuit, we can calculate the fidelity between the unitary operator this circuit implements, and the exact unitary. Before performing the computation, we first reduce the transpiled circuit, which contains 127 qubits, down to a circuit that only contains the active qubits, of which there are two.

import numpy as np
 
 
def trace_to_fidelity_2q(trace: float) -> float:
    return (4.0 + trace * trace.conjugate()) / 20.0
 
 
# Reduce circuits down to 2 qubits so they are easy to simulate
qc_t3_exact_small = QuantumCircuit.from_instructions(qc_t3_exact)
qc_t3_approx_small = QuantumCircuit.from_instructions(qc_t3_approx)
 
# Compute the fidelity
exact_fid = trace_to_fidelity_2q(
    np.trace(np.dot(Operator(qc_t3_exact_small).adjoint().data, UU))
)
approx_fid = trace_to_fidelity_2q(
    np.trace(np.dot(Operator(qc_t3_approx_small).adjoint().data, UU))
)
print(
    f"Synthesis fidelity\nExact: {exact_fid:.3f}\nApproximate: {approx_fid:.3f}"
)

Output:

Synthesis fidelity
Exact: 1.000
Approximate: 0.992

Adjusting the optimization level can change other aspects of the circuit too, not just the number of ECR gates. For examples of how setting optimization level changes the layout, see Representing quantum computers.


Next steps

Recommendations