Active Qudit Multistate Reset with SHF instruments¶
In this notebook, you will learn how to execute active reset of a multistate qudit, i.e. active feedback based on real-time measurement of the qudit state. We require either a SHFQC instrument for this notebook or a combination of SHFSG and SHFQA connected via a PQSC.
This demonstration runs without real qubits, assuming a loopback on the readout drive line directly into the readout acquisition line. We emulate the different qubit states by up to four different readout measurement pulses, differing by a phase. To demonstrate real-time feedback, we first calibrate the state discrimination unit for the four measurement pulses with which we choose to emulate the qudit response. Then we use this calibration to play an arbitrary simulated pattern of qudit states and demonstrate the real-time feedback capabilities of the instrument.
0. General Imports and Definitions¶
0.1 Python Imports¶
import matplotlib.pyplot as plt
import numpy as np
from laboneq.analysis import calculate_integration_kernels_thresholds
from laboneq.contrib.example_helpers.feedback_helper import (
create_calibration_experiment,
create_discrimination_experiment,
piecewise_modulated,
state_emulation_pulse,
)
from laboneq.contrib.example_helpers.generate_device_setup import (
generate_device_setup_qubits,
)
from laboneq.dsl.experiment.builtins import *
# LabOne Q:
from laboneq.simple import *
1. Device setup and calibration¶
1.1 Generate a calibrated Device Setup and qubit objects¶
feedback_type = "global"
# feedback_type = "local"
# specify the number of qubits you want to use
number_of_qubits = 6
# 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, q2, q3 = qubits[:4]
1.2 Adapt setup calibration¶
In this notebook we are using pulses played from an additional set of measure lines to emulate the qudit being in the excited state. In this case we want to have the same instrument settings for the four used measurement lines. Additionally, for the method of readout weight calibration demonstrated in this notebook, the acquire line should not be modulated, as the calculated readout weights already contain the software modulation by construction.
active_reset_calibration = Calibration()
active_reset_calibration["/logical_signal_groups/q1/measure"] = (
device_setup.get_calibration()["/logical_signal_groups/q0/measure"]
)
active_reset_calibration["/logical_signal_groups/q2/measure"] = (
device_setup.get_calibration()["/logical_signal_groups/q0/measure"]
)
active_reset_calibration["/logical_signal_groups/q3/measure"] = (
device_setup.get_calibration()["/logical_signal_groups/q0/measure"]
)
active_reset_calibration["/logical_signal_groups/q0/acquire"] = (
device_setup.get_calibration()["/logical_signal_groups/q0/acquire"]
)
active_reset_calibration["/logical_signal_groups/q0/acquire"].oscillator = None
# print(active_reset_calibration)
device_setup.set_calibration(active_reset_calibration)
# print(device_setup.get_calibration())
# use emulation mode - no connection to instruments
use_emulation = True
# create and connect to a LabOne Q session
my_session = Session(device_setup=device_setup)
my_session.connect(do_emulation=use_emulation)
2. Calibration of state discrimination¶
We determine the optimal integration weights by measuring traces of the four qubit states (ground state, e, f, and h) and computing integration kernels using the toolkit routines. We simulate different qubit responses by playing pulses with different phases and amplitudes on the readout line. We have to make sure that the traces are a multiple of 16 samples long.
2.1 Obtain traces¶
num_states = 4
used_qubits = [q0, q1, q2, q3][:num_states]
calibration_experiment = create_calibration_experiment(
state_emulation_pulse=state_emulation_pulse(),
qubit_states=range(num_states),
measure_signals=[q.signals["measure"] for q in used_qubits],
acquire_signal=q0.signals["acquire"],
)
calibration_results = my_session.run(calibration_experiment)
calibration_traces = []
for it in range(num_states):
trace = calibration_results.get_data(f"raw_{it}")
calibration_traces.append(trace[: (len(trace) // 16) * 16])
# In emulation mode, the 'acquired' traces are all identical. Consequently, the computation of the optimal
# discrimination weights will fail. Instead we 'patch' the traces with an artificial phase.
if use_emulation:
for i in range(num_states):
phase = np.exp(2j * np.pi * i / num_states)
calibration_traces[i] *= phase
2.2 Compute kernels¶
Using calculate_integration_kernels_thresholds, we get number of states minus 1 optimal integration kernels together with the optimal thresholds for state discrimination.
# Calculate and plot kernels
kernels, thresholds = calculate_integration_kernels_thresholds(calibration_traces)
for i, k in enumerate(kernels):
plt.plot(k.samples.real, k.samples.imag, ["ro-", "gx-", "b+-"][i], alpha=0.2)
# set the thresholds in the acquire line calibration
threshold_calibration = Calibration()
threshold_calibration["/logical_signal_groups/q0/acquire"] = SignalCalibration(
threshold=thresholds
)
device_setup.set_calibration(threshold_calibration)
# print(device_setup.get_calibration())
2.3 Verify state discrimination¶
my_exp = create_discrimination_experiment(
measure_lines=[
q0.signals["measure"],
q1.signals["measure"],
q2.signals["measure"],
q3.signals["measure"],
],
acquire_line=q0.signals["acquire"],
kernels=kernels,
state_emulation_pulse=state_emulation_pulse,
thresholds=thresholds,
)
discrimination_results = my_session.run(my_exp)
s0 = discrimination_results.get_data("data_0").real
s1 = discrimination_results.get_data("data_1").real
s2 = discrimination_results.get_data("data_2").real
s3 = discrimination_results.get_data("data_3").real
plt.plot(s0, ".b")
plt.plot(s1, ".r")
plt.plot(s2, ".g")
plt.plot(s3, ".k")
3. Feedback experiment¶
Here, we create a real-time feedback demonstration that plays back a user defined sequence of "qubit states", i.e., a sequences of different measurement pulses emulating different qubit states. The measured qubit state after state discrimination is used in a real-time feedback section to play back one of four pulses.
# define pulses
pulse_length = q0.parameters.custom["pulse_length"]
x90_eg = pulse_library.drag(uid="x90_eg", length=pulse_length)
x180_eg = pulse_library.sampled_pulse(
uid="x180_eg",
samples=piecewise_modulated(
piece_length=[pulse_length],
piece_frequency=[0],
piece_amplitude=[0.3],
),
)
x180_fg = pulse_library.sampled_pulse(
uid="x180_fg",
samples=piecewise_modulated(
piece_length=[pulse_length, pulse_length],
piece_frequency=[-200e6, 0],
piece_amplitude=[0.6, 0.3],
),
)
x180_hg = pulse_library.sampled_pulse(
uid="x180_hg",
samples=piecewise_modulated(
piece_length=[pulse_length, pulse_length, pulse_length],
piece_frequency=[-400e6, -200e6, 0],
piece_amplitude=[0.9, 0.6, 0.3],
),
)
plt.plot(x180_hg.samples.real)
plt.plot(x180_fg.samples.real)
plt.plot(x180_eg.samples.real)
3.1 Define Experiment¶
def create_feedback_experiment(
feedback_pattern="0102 2313 1031",
kernels=kernels,
num_average=4,
space_delay=400e-9,
pattern_delay=1000e-9,
acquire_delay=150e-9,
state_emulation_pulse=state_emulation_pulse,
reset_pulses=(x90_eg, x180_eg, x180_fg, x180_hg),
acquisition_type=AcquisitionType.DISCRIMINATION,
):
# Pattern example: "0102 2313 1031"
# with:
# 0 - ground state
# 1 - first excited state
# 2 - second excited state
# 3 - third excited state
# (empty space) break between symbols
@experiment(
signals=[
"drive",
"measure_g",
"measure_e",
"measure_f",
"measure_h",
"acquire",
]
)
def exp():
map_signal("drive", q0.signals["drive"])
map_signal("measure_g", q0.signals["measure"])
map_signal("measure_e", q1.signals["measure"])
map_signal("measure_f", q2.signals["measure"])
map_signal("measure_h", q3.signals["measure"])
map_signal("acquire", q0.signals["acquire"])
measure_emulation_pulse = state_emulation_pulse()
with acquire_loop_rt(
count=num_average,
acquisition_type=acquisition_type,
uid="shots",
):
# iterate over the letters of the given pattern
last = None
identifier = 0
for identifier, letter in enumerate(feedback_pattern):
if letter == " ":
with section(uid=f"delay_{identifier}", play_after=last):
delay(signal="drive", time=space_delay)
last = f"delay_{identifier}"
continue
# emulate qubit state by playing different measurement pulses based on pattern
with section(uid=f"measure_{identifier}", play_after=last):
idx = {"0": 0, "1": 1, "2": 2, "3": 3}[letter]
line = ["measure_g", "measure_e", "measure_f", "measure_h"][idx]
play(
signal=line,
pulse=measure_emulation_pulse.pulse,
phase=measure_emulation_pulse.pulse_phase(qubit_state=idx),
amplitude=measure_emulation_pulse.pulse_amplitude(
qubit_state=idx
),
)
acquire(signal="acquire", handle="qubit_state", kernel=kernels)
last = f"measure_{identifier}"
# delay after state discrimination and before reset pulse playback
if acquire_delay > 0:
with section(uid=f"acquire_delay_{identifier}", play_after=last):
reserve(signal="acquire")
delay(signal="drive", time=acquire_delay)
last = f"acquire_delay_{identifier}"
# real-time feedback, fetching the measurement data identified by handle from the QA unit specified in the descriptor
# determines automatically if local (SHFQC only) of global (through PQSC) feedback path is to be used
with match(
uid=f"feedback_{identifier}", handle="qubit_state", play_after=last
):
with case(state=0):
play(signal="drive", pulse=reset_pulses[0])
with case(state=1):
play(signal="drive", pulse=reset_pulses[1])
if len(kernels) > 1:
with case(state=2):
play(signal="drive", pulse=reset_pulses[2])
if len(kernels) > 2:
with case(state=3):
play(signal="drive", pulse=reset_pulses[3])
last = f"feedback_{identifier}"
# introduce a delay between repetitions of the pattern, for visual distinction
with section(uid=f"pattern_delay{identifier}", play_after=last):
delay(signal="drive", time=pattern_delay)
last = f"pattern_delay{identifier}"
return exp()
3.2 Run experiment¶
my_feedback_exp = create_feedback_experiment(feedback_pattern="0102 2313 1031")
# compile experiment
my_compiled_exp = my_session.compile(my_feedback_exp)
# run experiment and get the results
my_results = my_session.run(my_compiled_exp)
## Look at th pulse sheet - feedback is characterised by multiple simultaneous sections
# show_pulse_sheet("feedback_experiment", my_compiled_exp)