Waveform Replacement¶
In this notebook, you'll learn how to use a callback function in a near-time sweep to perform a waveform replacement experiment. This kind of functionality can be adapted to your own experiment, e.g., VQE or optimal control.
0. General Imports and Definitions¶
# LabOne Q:
from laboneq.simple import *
# Helper files for fitting and plotting
from laboneq.contrib.example_helpers.plotting.plot_helpers import plot_simulation
from laboneq.contrib.example_helpers.example_notebook_helper import create_device_setup
# Other imports
from pathlib import Path
import numpy as np
1. Create Device Setup¶
Create the device setup from the descriptor, and apply some convenient mapping to instruments and logical signals.
device_setup = create_device_setup(generation=2)
# Define convenience shortcut to logical signals
lsg = {
qubit_name: device_setup.logical_signal_groups[qubit_name].logical_signals
for qubit_name in device_setup.logical_signal_groups.keys()
}
2. Create and Connect to a QCCS Session¶
# perform experiments in emulation mode only? - if True, also generate dummy data for fitting
emulate = True
# create and connect to a session
session = Session(device_setup=device_setup)
session.connect(do_emulation=emulate)
3. Pulse Exchange Experiment¶
3.1 Pulse Definitions¶
Below, we define the pulse definitions to be used in the experiment. The only restriction is that pulses must be of the same length of those that they are replacing.
pulse_length = 256 # in samples
pulse_time = 256 / 2.0e9 # in seconds
# first pulse - constant, square pulse
p_const = pulse_library.const(uid="const", length=pulse_time, amplitude=1)
# second pulse - gaussian
p_gauss = pulse_library.gaussian(
uid="gauss", length=pulse_time, amplitude=0.8, sigma=0.3
)
# third pulse - drag
p_drag = pulse_library.drag(
uid="drag", length=pulse_time, amplitude=1, sigma=0.3, beta=0.1
)
# user defined pulse
@pulse_library.register_pulse_functional
def flattop_gaussian(x, relative_length_flat=0.6, **_):
sigma = (1 - relative_length_flat) / 3
res = np.ones(len(x))
res[x <= -relative_length_flat] = np.exp(
-((x[x <= -relative_length_flat] + relative_length_flat) ** 2) / (2 * sigma**2)
)
res[x >= relative_length_flat] = np.exp(
-((x[x >= relative_length_flat] - relative_length_flat) ** 2) / (2 * sigma**2)
)
return res
p_flattop = flattop_gaussian(uid="flattop", length=pulse_time, amplitude=1)
3.2 Replace pulse function¶
Below, we define the function to replace our pulse with another.
def neartime_callback_to_replace_pulse(session: Session, idx):
# take pulse index and replace
if idx > 0.5 and idx < 1.5:
# replace library pulse with library pulse
session.replace_pulse(p_flattop, p_gauss)
print(f"{idx} First replacement: p_flattop replaced by p_gauss")
return
elif idx > 1.5 and idx < 2.5:
# replace library pulse with library pulse
session.replace_pulse(p_gauss, p_drag)
print(f"{idx} Second replacement: p_gauss replaced by p_drag")
return
elif idx > 2.5:
# replace library pulse with sampled pulse
session.replace_pulse(p_drag, p_const)
print(f"{idx} Third replacement: p_drag replaced by p_const")
return
print(idx)
return
3.3 Experiment definition¶
In our experiment, we increase a index (instance_idx
) where, once the index increases over the threshold set in the above near-time callback, once pulse is replaced with another.
def exp_waveform_exchange(
count,
lsg=lsg["q0"],
):
exp = Experiment(signals=[ExperimentSignal("drive")], uid="Exchange Experiment")
exp.set_signal_map({"drive": lsg["drive_line"]})
# set oscillator frequency to 0 for visibility of signal
lsg["drive_line"].calibration.oscillator.frequency = 0
instance_idx = LinearSweepParameter(
uid="x90_instance_idx", start=0, stop=3, count=count
)
# pulse index sweep in near time
with exp.sweep(uid="x90_tune", parameter=instance_idx):
# replace pulses
# acquisition loop
with exp.acquire_loop_rt(
uid="shots", count=1, acquisition_type=AcquisitionType.INTEGRATION
):
# play a sequence of pulses
with exp.section(uid="play"):
exp.play(signal="drive", pulse=p_flattop, phase=0)
exp.delay(signal="drive", time=25e-9)
exp.play(signal="drive", pulse=p_gauss)
exp.delay(signal="drive", time=25e-9)
exp.play(signal="drive", pulse=p_drag, phase=np.pi / 2.0, amplitude=0.3)
exp.delay(signal="drive", time=25e-9)
exp.play(signal="drive", pulse=p_gauss, length=50e-9)
exp.delay(signal="drive", time=0.5)
exp.call(neartime_callback_to_replace_pulse, idx=instance_idx)
return exp
3.4 Compilation¶
# register near-time callback in session
session.register_neartime_callback(neartime_callback_to_replace_pulse)
# compile
comp_waveform_replacement = session.compile(exp_waveform_exchange(30))
3.5 Simulation¶
Here, the simulation shows the first real-time pulse sequence, before the pulses are replaced using our near-time callback.
plot_simulation(comp_waveform_replacement, start_time=0, length=1e-6)
3.6 Create pulse sheet¶
Path("Pulse_sheets").mkdir(parents=True, exist_ok=True)
show_pulse_sheet("Pulse_sheets/waveform_replacement", comp_waveform_replacement)
3.7 Run experiment¶
# run the compiled experiemnt
waveform_replacement_results = session.run(comp_waveform_replacement)