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 readoud 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 discrimintation 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_example_datastore import (
generate_example_datastore,
get_first_named_entry,
)
from laboneq.dsl.experiment.builtins import *
# LabOne Q:
from laboneq.simple import *
# Build an in-memory data store with device setup and qubit parameters for the
# example notebooks
dummy_db = generate_example_datastore(in_memory=True)
use_emulation = True
1. Device setup and calibration¶
1.1 Load a calibrated Device Setup and qubit object¶
# Choose your setup - local feedback on a standalone SHFQC or ...
# feedback_type="local"
# my_setup = get_first_named_entry(db=dummy_db, name="6_qubit_setup_shfqc_calibrated")
# ... global feedback on a combination of SHFSG and SHFQA, connected through a PQSC
feedback_type = "global"
my_setup = get_first_named_entry(
db=dummy_db, name="12_tuneable_qubit_setup_shfsg_shfqa_shfqc_hdawg_pqsc_calibrated"
)
my_qubit = my_setup.qubits[0]
q0 = my_setup.logical_signal_groups["q0"].logical_signals
q1 = my_setup.logical_signal_groups["q1"].logical_signals
q2 = my_setup.logical_signal_groups["q2"].logical_signals
q3 = my_setup.logical_signal_groups["q3"].logical_signals
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_line"] = (
my_setup.get_calibration()["/logical_signal_groups/q0/measure_line"]
)
active_reset_calibration["/logical_signal_groups/q2/measure_line"] = (
my_setup.get_calibration()["/logical_signal_groups/q0/measure_line"]
)
active_reset_calibration["/logical_signal_groups/q3/measure_line"] = (
my_setup.get_calibration()["/logical_signal_groups/q0/measure_line"]
)
active_reset_calibration["/logical_signal_groups/q0/acquire_line"] = (
my_setup.get_calibration()["/logical_signal_groups/q0/acquire_line"]
)
active_reset_calibration["/logical_signal_groups/q0/acquire_line"].oscillator = None
# print(active_reset_calibration)
my_setup.set_calibration(active_reset_calibration)
# print(my_setup.get_calibration())
# create and connect to a LabOne Q session
my_session = Session(device_setup=my_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
experiments = [
create_calibration_experiment(
state_emulation_pulse=state_emulation_pulse(),
qubit_state=i,
measure_signal=l["measure_line"],
acquire_signal=q0["acquire_line"],
)
for i, l in enumerate([q0, q1, q2, q3])
]
traces = []
for exp in experiments:
res = my_session.run(exp)
trace = res.get_data("raw")
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)
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(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_line"] = SignalCalibration(
threshold=thresholds
)
my_setup.set_calibration(threshold_calibration)
# print(my_setup.get_calibration())
2.3 Verify state discrimination¶
my_exp = create_discrimination_experiment(
measure_lines=[
q0["measure_line"],
q1["measure_line"],
q2["measure_line"],
q3["measure_line"],
],
acquire_line=q0["acquire_line"],
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 = my_qubit.parameters.user_defined["pulse_length"]
x90_eg = pulse_library.drag(uid="x90_eg", length=pulse_length)
x180_eg = pulse_library.sampled_pulse_complex(
uid="x180_eg",
samples=piecewise_modulated(
piece_length=[pulse_length],
piece_frequency=[0],
piece_amplitude=[0.3],
),
)
x180_fg = pulse_library.sampled_pulse_complex(
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_complex(
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["drive_line"])
map_signal("measure_g", q0["measure_line"])
map_signal("measure_e", q1["measure_line"])
map_signal("measure_f", q2["measure_line"])
map_signal("measure_h", q3["measure_line"])
map_signal("acquire", q0["acquire_line"])
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
id = 0
for id, letter in enumerate(feedback_pattern):
#
if letter == " ":
with section(uid=f"delay_{id}", play_after=last):
delay(signal="drive", time=space_delay)
last = f"delay_{id}"
continue
# emulate qubit state by playing different measurement pulses based on pattern
with section(uid=f"measure_{id}", 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_{id}"
# delay after state discrimination and before reset pulse playback
if acquire_delay > 0:
with section(uid=f"acquire_delay_{id}", play_after=last):
reserve(signal="acquire")
delay(signal="drive", time=acquire_delay)
last = f"acquire_delay_{id}"
# 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_{id}", 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_{id}"
# introduce a delay between repetitions of the pattern, for visual distinction
with section(uid=f"pattern_delay{id}", play_after=last):
delay(signal="drive", time=pattern_delay)
last = f"pattern_delay{id}"
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)