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')
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]))