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.

Create a pass manager for dynamical decoupling

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

This page demonstrates how to use the PadDynamicalDecoupling pass to add an error suppression technique called dynamical decoupling to the circuit.

Dynamical decoupling works by adding pulse sequences (known as dynamical decoupling sequences) to idle qubits to flip them around the Bloch sphere, which cancels the effect of noise channels, thereby suppressing decoherence. These pulse sequences are similar to refocusing pulses used in nuclear magnetic resonance. For a full description, see A Quantum Engineer's Guide to Superconducting Qubits.

Because the PadDynamicalDecoupling pass only operates on scheduled circuits and involves gates that are not necessarily basis gates of our target, you will need the ALAPScheduleAnalysis and BasisTranslator passes as well.

This example uses ibm_brisbane, which was initialized previously. Get the target information from the backend and save the operation names as basis_gates because the target will need to be modified to add timing information for the gates used in dynamical decoupling.

from qiskit_ibm_runtime import QiskitRuntimeService
 
service = QiskitRuntimeService()
backend = service.backend("ibm_brisbane")
 
target = backend.target
basis_gates = list(target.operation_names)

Create an EfficientSU2 circuit as an example. First, transpile the circuit to the backend because dynamical decoupling pulses need to be added after the circuit has been transpiled and scheduled. Dynamical decoupling often works best when there is a lot of idle time in the quantum circuits - that is, there are qubits that are not being used while others are active. This is the case in this circuit because the two-qubit ecr gates are applied sequentially in this ansatz.

from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.circuit.library import EfficientSU2
 
qc = EfficientSU2(12, entanglement="circular", reps=1)
pm = generate_preset_pass_manager(1, target=target, seed_transpiler=12345)
qc_t = pm.run(qc)
qc_t.draw("mpl", fold=-1, idle_wires=False)

Output:

A dynamical decoupling sequence is a series of gates that compose to the identity and are spaced regularly in time. For example, start by creating a simple sequence called XY4 consisting of four gates.

from qiskit.circuit.library import XGate, YGate
 
X = XGate()
Y = YGate()
 
dd_sequence = [X, Y, X, Y]

Because of the regular timing of dynamical decoupling sequences, information about the YGate must be added to the target because it is not a basis gate, whereas the XGate is. We know a priori that the YGate has the same duration and error as the XGate, however, so we can just retrieve those properties from the target and add them back for the YGates. This is also why the basis_gates were saved separately, since we are adding the YGate instruction to the target although it is not an actual basis gate of ibm_brisbane.

from qiskit.transpiler import InstructionProperties
 
y_gate_properties = {}
for qubit in range(target.num_qubits):
    y_gate_properties.update(
        {
            (qubit,): InstructionProperties(
                duration=target["x"][(qubit,)].duration,
                error=target["x"][(qubit,)].error,
            )
        }
    )
 
target.add_instruction(YGate(), y_gate_properties)

Ansatz circuits such as EfficientSU2 are parameterized, so they must have value bound to them before being sent to the backend. Here, assign random parameters.

import numpy as np
 
rng = np.random.default_rng(1234)
qc_t.assign_parameters(
    rng.uniform(-np.pi, np.pi, qc_t.num_parameters), inplace=True
)

Next, execute the custom passes. Instantiate the PassManager with ALAPScheduleAnalysis and PadDynamicalDecoupling. Run ALAPScheduleAnalysis first to add timing information about the quantum circuit before the regularly-spaced dynamical decoupling sequences can be added. These passes are run on the circuit with .run().

from qiskit.transpiler import PassManager
from qiskit.transpiler.passes.scheduling import (
    ALAPScheduleAnalysis,
    PadDynamicalDecoupling,
)
 
dd_pm = PassManager(
    [
        ALAPScheduleAnalysis(target=target),
        PadDynamicalDecoupling(target=target, dd_sequence=dd_sequence),
    ]
)
qc_dd = dd_pm.run(qc_t)

Use the visualization tool timeline_drawer to see the circuit's timing and confirm that a regularly-spaced sequence of XGates and YGates appear in the circuit.

from qiskit.visualization import timeline_drawer
 
timeline_drawer(qc_dd, show_idle=False)

Output:

Lastly, because the YGate is not an actual basis gate of our backend, manually apply the BasisTranslator pass (this is a default pass, but it is executed before scheduling, so it needs to be applied again). The session equivalence library is a library of circuit equivalences that allows the transpiler to decompose circuits into basis gates, as also specified as an argument.

from qiskit.circuit.equivalence_library import (
    SessionEquivalenceLibrary as sel,
)
from qiskit.transpiler.passes import BasisTranslator
 
qc_dd = BasisTranslator(sel, basis_gates)(qc_dd)
qc_dd.draw("mpl", fold=-1, idle_wires=False)

Output:

Now, YGates are absent from our circuit, and there is explicit timing information in the form of Delay gates. This transpiled circuit with dynamical decoupling is now ready to be sent to the backend.


Next steps

Recommendations