Example: Dynamical decoupling

Example: Dynamical decoupling#

There is a standard compilation stage for automatically applying dynamical decoupling (DD) sequences to an instruction schedule, controlled by the dd_mode compiler option. By default the DD stage is disabled.

The iqm.cpc.compiler.dd module contains various utility functions the DD stage uses, e.g. for locating specific instructions in a schedule, constructing DD sequences, and replacing select instructions with them. For example, we can find locations of Wait instructions on qubit drive channels with a certain minimum duration, and replace them with an arbitrary sequence of Wait and IQPulse instructions that dynamically decouples the qubit, given that the total duration of the sequence is the same as the duration of the original Wait.

import os

from IPython.core.display import HTML
from qiskit import QuantumCircuit
from qiskit.compiler import transpile
from qiskit.result import marginal_distribution

from iqm.cpc.compiler.dd import STANDARD_DD_STRATEGY
from iqm.cpc.interface.compiler import (
    CircuitBoundaryMode,
    CircuitExecutionOptions,
    DDMode,
    HeraldingMode,
    MeasurementMode,
    MoveGateFrameTrackingMode,
    MoveGateValidationMode,
)

from iqm.pulla.pulla import Pulla
from iqm.pulla.utils_qiskit import qiskit_circuits_to_pulla, station_control_result_to_qiskit
from iqm.pulse.playlist.visualisation.base import inspect_playlist
from iqm.qiskit_iqm import IQMProvider
from iqm.qiskit_iqm.iqm_transpilation import optimize_single_qubit_gates
cocos_url = os.environ['PULLA_COCOS_URL']                     # or set the URL directly here
station_control_url = os.environ['PULLA_STATION_CONTROL_URL'] # or set the URL directly here

provider = IQMProvider(cocos_url)
backend = provider.get_backend()
p = Pulla(station_control_url=station_control_url)

Let’s create and transpile a quantum circuit that prepares a superposition on one qubit, and then executes a long sequence of gates on other qubits while the first qubit is idling and experiencing decoherence. Finally we reverse the superposition and measure the qubits.

qc = QuantumCircuit(3)
for _ in range(1):
    qc.h(0)
    qc.barrier()
for _ in range(100):
    qc.cz(1, 2)
    qc.barrier()
qc.h(0)
qc.measure_all()
qc.draw(output='mpl')
_images/442de2cdb9e33050214860478c10cb9f521bb79b02e8084d5fa99c56f7e5cc29.png
qc_transpiled = transpile(qc, backend=backend, optimization_level=1)
qc_optimized = optimize_single_qubit_gates(qc_transpiled)
c = qiskit_circuits_to_pulla(qc_optimized, qubit_idx_to_name=backend._idx_to_qb)

shots = 10000

Now let us create a pair of Pulla compilers, one with DD and one without.

component_mapping = {str(idx): qb for idx, qb in backend._idx_to_qb.items()}

standard_compiler = p.get_standard_compiler()
standard_compiler.component_mapping = component_mapping

custom_compiler = p.get_standard_compiler()
custom_compiler.options = CircuitExecutionOptions(
    measurement_mode=MeasurementMode.ALL,
    heralding_mode=HeraldingMode.NONE,
    dd_mode=DDMode.ENABLED,
    dd_strategy=STANDARD_DD_STRATEGY,
    circuit_boundary_mode=CircuitBoundaryMode.ALL,
    move_gate_frame_tracking=MoveGateFrameTrackingMode.FULL,
    move_gate_validation=MoveGateValidationMode.STRICT,
    active_reset_cycles=None,
)
custom_compiler.component_mapping = component_mapping

Compiling the circuit using the non-DD compiler produces a schedule with a long wait on QB1__drive.awg.

playlist, context = standard_compiler.compile(c)
settings, context = standard_compiler.build_settings(context, shots=shots)
HTML(inspect_playlist(playlist, [0]))

We then execute this schedule and compute the probability of finding QB1 in the |0> state. Ideally this should be one, but due to the decoherence experienced by the superposition state it will be less.

response_without_dd = p.execute(playlist, context, settings, verbose=False)
qiskit_result = station_control_result_to_qiskit(
    response_without_dd, shots=shots, execution_options=standard_compiler.options
)
counts_orig = qiskit_result.get_counts()
print(f"\nQiskit result counts:\n{counts_orig}\n")

prob = marginal_distribution(counts_orig, indices=[0])['0'] / shots
print(f"Probability of finding qubit 0 it the |0> state (original circuit): {prob}")
[12-04 17:06:23;I] Submitted sweep with ID: 8615e857-85bd-4f26-8164-88f0a5b02b23
[12-04 17:06:23;I] Created task in queue with ID: 62ec8738-bc36-42d2-b91e-a5a6c76d8aeb
[12-04 17:06:23;I] Sweep link: http://xld11-s2.xld11.iqm.fi/station/sweeps/8615e857-85bd-4f26-8164-88f0a5b02b23
[12-04 17:06:23;I] Task link: http://xld11-s2.xld11.iqm.fi/station/tasks/62ec8738-bc36-42d2-b91e-a5a6c76d8aeb
[12-04 17:06:23;I] Waiting for the sweep to finish...
[12-04 17:06:30;I] Sweep status: SweepStatus.SUCCESS

Qiskit result counts:
{'001': 2721, '000': 6919, '100': 194, '101': 73, '010': 66, '011': 25, '111': 1, '110': 1}

Probability of finding qubit 0 it the |0> state (original circuit): 0.718

Compiling the circuit using the DD compiler instead produces a schedule where the long wait on QB1__drive.awg is replaced by a DD sequence.

playlist, context = custom_compiler.compile(c)
settings, context = custom_compiler.build_settings(context, shots=shots)

We execute the DD schedule and again compute the probability of finding QB1 in the |0> state, and will likely see that the probability has gone up, due to the DD sequence partially decoupling the idling QB1 from various decoherence sources and better preserving the superposition state.

response_with_dd = p.execute(playlist, context, settings, verbose=False)
qiskit_result = station_control_result_to_qiskit(
    response_with_dd, shots=shots, execution_options=custom_compiler.options
)
counts_dd = qiskit_result.get_counts()
print(f"\nQiskit result counts:\n{counts_dd}\n")

prob = marginal_distribution(counts_dd, indices=[0])['0'] / shots
print(f"Probability of finding qubit 0 it the |0> state (dynamically decoupled circuit): {prob}")
[12-04 17:06:32;I] Submitted sweep with ID: 0ba4d352-5e48-4260-bfb1-7e5e4fe45eb6
[12-04 17:06:32;I] Created task in queue with ID: 411524e8-a2d1-4a6a-bc9b-2bbc72d21b92
[12-04 17:06:32;I] Sweep link: http://xld11-s2.xld11.iqm.fi/station/sweeps/0ba4d352-5e48-4260-bfb1-7e5e4fe45eb6
[12-04 17:06:32;I] Task link: http://xld11-s2.xld11.iqm.fi/station/tasks/411524e8-a2d1-4a6a-bc9b-2bbc72d21b92
[12-04 17:06:32;I] Waiting for the sweep to finish...
[12-04 17:06:37;I] Sweep status: SweepStatus.SUCCESS

Qiskit result counts:
{'000': 8888, '001': 721, '100': 282, '110': 5, '010': 75, '101': 20, '011': 7, '111': 2}

Probability of finding qubit 0 it the |0> state (dynamically decoupled circuit): 0.925

We can see that the long wait on QB1__drive.awg was indeed replaced by a DD sequence.

HTML(inspect_playlist(playlist, [0]))