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.
General Imports and Definitions¶
# LabOne Q:
# Other imports
from pathlib import Path
import numpy as np
# Helper files for fitting and plotting
from laboneq.contrib.example_helpers.generate_device_setup import (
generate_device_setup_qubits,
)
from laboneq.contrib.example_helpers.plotting.plot_helpers import plot_simulation
from laboneq.simple import *
Create Device Setup¶
Generate the device setup and some qubit objects from pre-defined parameters in a helper file
# 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,
shfqc=[
{
"serial": "DEV12001",
"zsync": 1,
"number_of_channels": 6,
"readout_multiplex": 6,
"options": None,
}
],
include_flux_lines=False,
server_host="localhost",
setup_name=f"my_{number_of_qubits}_fixed_qubit_setup",
)
q0, q1 = qubits[: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)
Pulse Exchange Experiment¶
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)
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
Experiment definition¶
In our experiment, we increase an 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,
qubit=q0,
):
exp = Experiment(
signals=[ExperimentSignal("drive", map_to=qubit.signals["drive"])],
uid="Exchange Experiment",
)
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):
# 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)
# replace pulses with a callback function
exp.call(neartime_callback_to_replace_pulse, idx=instance_idx)
calibration = Calibration({"drive": qubit.calibration()[f"{qubit.uid}/drive"]})
exp.set_calibration(calibration)
return exp
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))
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)
Create pulse sheet¶
Path("Pulse_sheets").mkdir(parents=True, exist_ok=True)
show_pulse_sheet("Pulse_sheets/waveform_replacement", comp_waveform_replacement)
Run experiment¶
# run the compiled experiemnt
waveform_replacement_results = session.run(comp_waveform_replacement)