Randomized Benchmarking¶
An advanced use case example - Randomized benchmarking using the Clifford group
One applies random sequences of Clifford gates for different sequence lengths followed by a recovery gate - the resulting decay of the state fidelity as function of sequence length is a measure of overall gate fidelity
0. General Imports and Definitions¶
0.1 Python Imports¶
from __future__ import annotations
import matplotlib.pyplot as plt
import numpy as np
from laboneq.contrib.example_helpers.generate_device_setup import (
generate_device_setup_qubits,
)
from laboneq.contrib.example_helpers.plotting.plot_helpers import plot_simulation
# Helpers:
# additional imports needed for Clifford gate calculation
from laboneq.contrib.example_helpers.randomized_benchmarking_helper import (
clifford_parametrized,
generate_play_rb_pulses,
make_pauli_gate_map,
)
# LabOne Q:
from laboneq.simple import *
1. Setting up the LabOne Q Software¶
Define the device setup, experimental parameters and baseline calibration
Establish a session and connect to it
1.1 Generate Device Setup and qubit objects¶
# specify the number of qubits you want to use
number_of_qubits = 2
# generate the device setup and the qubit objects using a helper function
device_setup, qubits = generate_device_setup_qubits(
number_qubits=number_of_qubits,
pqsc=[{"serial": "DEV10001"}],
hdawg=[
{
"serial": "DEV8001",
"number_of_channels": 8,
"options": None,
}
],
shfqc=[
{
"serial": "DEV12001",
"number_of_channels": 6,
"readout_multiplex": 6,
"options": None,
}
],
include_flux_lines=True,
server_host="localhost",
setup_name=f"my_{number_of_qubits}_tuneable_qubit_setup",
)
[q0, q1] = qubits
1.2 Create a Session and Connect to it¶
emulate = True # perform experiments in emulation mode only?
my_session = Session(device_setup=device_setup)
my_session.connect(do_emulation=emulate)
2. Randomized Benchmarking¶
Perform a randomized benchmarking experiment on a qubit
2.1 Additional Experimental Parameters and Pulses¶
Define the number of averages and the pulses used in the experiment
# qubit readout pulse
readout_pulse = pulse_library.const(
uid="readout_pulse",
length=q0.parameters.custom["readout_length"],
amplitude=q0.parameters.custom["readout_amplitude"],
)
# integration weights for qubit measurement
integration_kernel = pulse_library.const(
uid="readout_weighting_function",
length=q0.parameters.custom["readout_length"],
amplitude=1.0,
)
2.1.1 Adjust Pulse Parameters for Clifford Gates¶
Define and prepare the basic gate set and the pulse objects corresponding to them
pulse_reference = pulse_library.gaussian
pulse_parameters = {"sigma": 1 / 3}
pulse_length = 64e-9
gate_map = make_pauli_gate_map(
pi_pulse_amp=0.8,
pi_half_pulse_amp=0.42,
excitation_length=pulse_length,
pulse_factory=pulse_reference,
pulse_kwargs=pulse_parameters,
)
2.2 Define and run the RB Experiment¶
The RB experiment will consist of random sequences of different lengths, where each sequence length contains a number of random instances.
Create Randomized Benchmarking Experiment¶
In real time (within acquire_loop_rt), the sequence lengths are swept, and for each sequence length, n_sequences_per_length random sequences are created.
Each random sequence consists of three sections:
- A right-aligned drive section, which is populated by the helper function
generate_play_rb_pulses - A readout section
- A relax section
generate_play_rb_pulses first creates a random sequence of Clifford gates together with the recovery gate. Then, the Clifford gates in the sequence are decomposed into the basic gate set and played via an Experiment.play command.
The handle in the acquire command follows the sequence length, facilitating straight-forward result processing after the experiment.
# define a convenience function to generate the RB sequences
def sweep_rb_pulses(
sequence_length: SweepParameter | LinearSweepParameter,
exp: Experiment,
signal: str,
cliffords,
gate_map,
rng,
):
with exp.match(sweep_parameter=sequence_length):
for v in sequence_length.values:
with exp.case(v):
generate_play_rb_pulses(
exp=exp,
signal=signal,
seq_length=v,
cliffords=cliffords,
gate_map=gate_map,
rng=rng,
)
# define the RB experiment
def define_rb_experiment(
num_average=2**8,
min_sequence_exponent=1,
max_sequence_exponent=8,
chunk_count=1,
n_sequences_per_length=2,
qubit=q0,
pulse_length=pulse_length,
readout_pulse=readout_pulse,
integration_kernel=integration_kernel,
prng=None,
):
# construct the sweep over sequence length as powers of 2 of the sequence exponent
sequence_length_sweep = SweepParameter(
values=np.array(
[2**it for it in range(min_sequence_exponent, max_sequence_exponent + 1)]
)
)
# we are using fixed timing, where the maximum duration is determined by the maximum sequence length
max_seq_duration = (2**max_sequence_exponent+1) * 3 * pulse_length
prng = np.random.default_rng(seed=42) if prng is None else prng
exp_rb = Experiment(
uid="RandomizedBenchmark",
signals=[
ExperimentSignal("drive", map_to=qubit.signals["drive"]),
ExperimentSignal("measure", map_to=qubit.signals["measure"]),
ExperimentSignal("acquire", map_to=qubit.signals["acquire"]),
],
)
# outer loop - real-time, cyclic averaging in discrimination mode
with exp_rb.acquire_loop_rt(
uid="rb_shots",
count=num_average,
averaging_mode=AveragingMode.CYCLIC,
acquisition_type=AcquisitionType.DISCRIMINATION,
):
# inner loop - sweep over sequence lengths
with exp_rb.sweep(
parameter=sequence_length_sweep,
chunk_count=chunk_count,
) as sequence_length:
# innermost loop - different random sequences for each length
## KNOWN ISSUE: using a sweep instead of the for loop here will lead to unchanged sequences
for num in range(n_sequences_per_length):
# with exp_rb.sweep(parameter=iteration_sweep):
with exp_rb.section(
uid=f"drive_{num}",
length=max_seq_duration,
alignment=SectionAlignment.RIGHT,
):
sweep_rb_pulses(
sequence_length,
exp_rb,
"drive",
clifford_parametrized,
gate_map,
prng,
)
# readout and data acquisition
with exp_rb.section(uid=f"measure_{num}", play_after=f"drive_{num}"):
exp_rb.measure(
measure_pulse=readout_pulse,
measure_signal="measure",
acquire_signal="acquire",
handle="rb_results",
integration_kernel=integration_kernel,
reset_delay=qubit.parameters.custom["reset_delay_length"],
)
exp_rb.reserve(signal="drive")
return exp_rb
# Initialize PRNG
my_prng = np.random.default_rng(42)
exp_rb = define_rb_experiment(max_sequence_exponent=3, chunk_count=1)
# compile the experiment
compiled_exp_rb = my_session.compile(exp_rb)
# KNOWN ISSUE - pulse sheet viewer not working for this experiment
# show_pulse_sheet('rb_experiment', compiled_exp_rb)
## KNOWN ISSUE - output simulation is not yet supported with piplined experiments, if chunk_count>1
plot_simulation(
compiled_exp_rb,
start_time=0,
length=10e-6,
plot_width=15,
plot_height=4,
signals=["drive", "measure"],
)
my_results = my_session.run(compiled_exp_rb)
3. Process Results and Plot¶
For each sequence length, the acquired results are averaged and then plotted.
rb_axis = my_results.get_axis("rb_results")
rb_results = my_results.get_data("rb_results")
rb_results
# plt.figure()
plt.plot(rb_axis[0], np.mean(rb_results, axis=1))
plt.xlabel("Sequence Length")
plt.ylabel("Average Fidelity")
plt.show()