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.station_control.interface.models import MoveGateFrameTrackingMode
from iqm.pulla.pulla import Pulla
from iqm.pulla.utils_qiskit import qiskit_to_pulla, sweep_job_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
iqm_server_url = os.environ['PULLA_IQM_SERVER_URL'] # or set the URL directly here
os.environ["IQM_TOKEN"] = os.environ.get("IQM_TOKEN") # or set the token directly here"
provider = IQMProvider(iqm_server_url)
backend = provider.get_backend()
p = Pulla(iqm_server_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)
circuits, compiler = qiskit_to_pulla(p, backend, qc_optimized)
Now let us create a pair of Compiler settings objects, one with DD turned on and one without.
standard_compiler = p.get_standard_compiler()
standard_settings = standard_compiler.get_settings(circuits=circuits)
dd_settings = standard_compiler.get_settings(circuits=circuits)
There are two ways to define a DD sequence. First, by changing the attributes of the dynamical_decoupling compilation stage. Let’s look at the available settings.
standard_settings.stages.dynamical_decoupling.apply_dd_strategy
dd_settings.stages.dynamical_decoupling.apply_dd_strategy.dd_is_disabled = False
dd_settings.stages.dynamical_decoupling.apply_dd_strategy.DDStrategy_gate_sequences_ratio = [9, 5, 2]
dd_settings.stages.dynamical_decoupling.apply_dd_strategy.DDDStrategy_gate_sequences_gate_pattern_xy = [
"XYXYYXYX",
"YXYX",
"XX",
]
dd_settings.stages.dynamical_decoupling.apply_dd_strategy.DDStrategy_gate_sequences_align = ["asap", "asap", "center"]
dd_settings.stages.timebox_stage.prepend_heralding.add_heralding = False
dd_settings.stages.timebox_stage.prepend_reset.active_reset_cycles = None
dd_settings.stages.circuit_stage.subscribe_and_probe.convert_terminal_measurements = True
dd_settings.stages.schedule_stage.apply_move_gate_phase_corrections.move_gate_frame_tracking_mode = MoveGateFrameTrackingMode.FULL
dd_settings.stages.circuit_stage.subscribe_and_probe.probe_all = True
dd_settings.stages.circuit_stage.validate_circuits.move_gate_validation = True
Compiling the circuit using the non-DD compiler produces a schedule with a long wait on QB1__drive.awg.
standard_job_definition, standard_context = standard_compiler.compile(circuits=circuits, settings=standard_settings)
standard_playlist = standard_job_definition.sweep_definition.playlist
HTML(inspect_playlist(standard_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.
standard_job = p.submit_playlist(standard_job_definition, context=standard_context)
standard_job.wait_for_completion()
qiskit_result = sweep_job_to_qiskit(
standard_job, shots=standard_settings.controllers.options.playlist_repeats.value
)
counts_orig = qiskit_result.get_counts()
print(f"\nQiskit result counts:\n{counts_orig}\n")
prob = marginal_distribution(counts_orig, indices=[0])['0'] / standard_settings.controllers.options.playlist_repeats.value
print(f"Probability of finding qubit 0 it the |0> state (original circuit): {prob}")
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.
dd_job_definition, dd_context = standard_compiler.compile(circuits=circuits, settings=dd_settings)
dd_playlist = dd_job_definition.sweep_definition.playlist
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.
dd_job = p.submit_playlist(dd_job_definition, context=dd_context)
dd_job.wait_for_completion()
qiskit_result = sweep_job_to_qiskit(
dd_job, shots=dd_settings.controllers.options.playlist_repeats.value
)
counts_dd = qiskit_result.get_counts()
print(f"\nQiskit result counts:\n{counts_dd}\n")
prob = marginal_distribution(counts_dd, indices=[0])['0'] / dd_settings.controllers.options.playlist_repeats.value
print(f"Probability of finding qubit 0 it the |0> state (dynamically decoupled circuit): {prob}")
We can see that the long wait on QB1__drive.awg was indeed replaced by a DD sequence.
HTML(inspect_playlist(dd_playlist, [0]))
You can also define the DD strategy as part of the compilation context.
# First, you construct the DDStrategy object
from iqm.station_control.interface.models import DDStrategy
dd_strategy = DDStrategy(
gate_sequences=[(9, "XYXYYXYX", "asap"), (5, "YXYX", "asap"), (2, "XX", "center")],
target_qubits=["QB1"],
)
dd_settings.stages.dynamical_decoupling.apply_dd_strategy.dd_is_disabled = False
dd_job_definition, dd_context = standard_compiler.compile(
circuits=circuits,
settings=dd_settings,
context={"dd_strategy": dd_strategy}
)
dd_playlist = dd_job_definition.sweep_definition.playlist
dd_job = p.submit_playlist(dd_job_definition)
dd_job.wait_for_completion()
qiskit_result = sweep_job_to_qiskit(
dd_job, shots=dd_settings.controllers.options.playlist_repeats.value
)
counts_dd = qiskit_result.get_counts()
print(f"\nQiskit result counts:\n{counts_dd}\n")
prob = marginal_distribution(counts_dd, indices=[0])['0'] / dd_settings.controllers.options.playlist_repeats.value
print(f"Probability of finding qubit 0 it the |0> state (dynamically decoupled circuit): {prob}")
HTML(inspect_playlist(dd_playlist, [0]))