Pulse Library and Sampled Pulses¶
This notebook demonstrates the features of the pulse library and will cover:
- How to use pre-defined pulses out of the box, and how to sweep their parameters here
- How to define your own, parameterized pulses and sweep their parameters here
- How to define sampled pulses, e.g., from a
numpy
array here
A demonstration of this notebook is also available on our Youtube channel here
Setting up: Imports, Device Setup, and Calibration¶
Imports¶
# LabOne Q:
from laboneq.simple import *
# Helpers:
from laboneq.contrib.example_helpers.plotting.plot_helpers import plot_simulation
from laboneq.contrib.example_helpers.descriptors.shfqc import descriptor_shfqc
from laboneq.contrib.example_helpers.descriptors.hdawg_uhfqa_pqsc import (
descriptor_hdawg_uhfqa_pqsc,
)
from laboneq.contrib.example_helpers.descriptors.shfsg_shfqa_pqsc import (
descriptor_shfsg_shfqa_pqsc,
)
import numpy as np
Define the Device Setup¶
We'll load a descriptor file to define our device setup and logical signal lines. We could, instead, explicitly include the descriptor here as a string and then use DeviceSetup.from_descriptor()
below. Choose the best method that works for you!
# Define and Load our Device Setup
device_setup = DeviceSetup.from_descriptor(
descriptor_shfsg_shfqa_pqsc,
server_host="ip_address", # ip address of the LabOne dataserver used to communicate with the instruments
server_port="8004", # port number of the dataserver - default is 8004
setup_name="my_QCCS_setup", # setup name
)
use_emulation = True # set to False to run on real hardware
Calibration¶
We go for a minimal signal line calibration here -- please refer to our calibration_reference.ipynb
notebook for detailed info.
# Basic calibration of IF and LO frequencies
drive_q0_lo = Oscillator(
uid="drive" + "q0" + "lo",
frequency=5.0e9,
)
drive_q0_if = Oscillator(
uid="drive" + "q0" + "if", frequency=1.0e8, modulation_type=ModulationType.HARDWARE
)
measure_q0_lo = Oscillator(uid="measure" + "q0" + "lo", frequency=5.5e9)
measure_q0_if = Oscillator(
uid="measure" + "q0" + "if", frequency=30e6, modulation_type=ModulationType.SOFTWARE
)
def calibrate_devices(device_setup):
## qubit 0
# calibration setting for drive line for qubit 0
device_setup.logical_signal_groups["q0"].logical_signals[
"drive_line"
].calibration = SignalCalibration(
# oscillator settings - frequency and type of oscillator used to modulate the pulses applied through this signal line
oscillator=drive_q0_if,
local_oscillator=drive_q0_lo,
range=10,
)
device_setup.logical_signal_groups["q0"].logical_signals[
"measure_line"
].calibration = SignalCalibration(
oscillator=measure_q0_if, local_oscillator=measure_q0_lo, range=10
)
device_setup.logical_signal_groups["q0"].logical_signals[
"acquire_line"
].calibration = SignalCalibration(
oscillator=measure_q0_if, local_oscillator=measure_q0_lo, range=5
)
calibrate_devices(device_setup)
Create the Session and Connect to it¶
session = Session(device_setup=device_setup)
session.connect(do_emulation=use_emulation)
Sweep Parameters Of an Out-of-the-Box Pulse¶
In a first step, we will create a drag pulse and sweep its amplitude. In a second step, we sweep the drag parameter beta instead.
Define Pulses¶
# qubit drive pulse
x90 = pulse_library.drag(uid="drag_pulse", length=400e-9, amplitude=1.0, beta=0.3)
# measure pulse
readout_pulse = pulse_library.const(uid="readout_pulse", length=200e-9, amplitude=1.0)
# readout integration weights
readout_weighting_function = pulse_library.const(
uid="readout_weighting_function", length=200e-9, amplitude=1.0
)
start = 0.1
stop = 1
count = 5
amplitude_sweep = LinearSweepParameter(
uid="amplitude", start=start, stop=stop, count=count
)
Define Experiment¶
Note that the amplitude
parameter is an argument of the Experiment.play
command.
# Create Experiment
exp = Experiment(
uid="Amplitude Rabi",
signals=[
ExperimentSignal("drive"),
ExperimentSignal("measure"),
ExperimentSignal("acquire"),
],
)
## experimental pulse sequence
# outer loop - real-time, cyclic averaging in standard integration mode
with exp.acquire_loop_rt(
uid="shots",
count=2**5,
averaging_mode=AveragingMode.CYCLIC,
acquisition_type=AcquisitionType.INTEGRATION,
):
# inner loop - real-time sweep of qubit drive pulse amplitude
with exp.sweep(
uid="sweep", parameter=amplitude_sweep, alignment=SectionAlignment.RIGHT
):
# qubit excitation - pulse amplitude will be swept
with exp.section(uid="qubit_excitation", alignment=SectionAlignment.RIGHT):
exp.play(signal="drive", pulse=x90, amplitude=amplitude_sweep)
# qubit readout pulse and data acquisition
with exp.section(uid="qubit_readout"):
exp.reserve(signal="drive")
# play readout pulse
exp.play(signal="measure", pulse=readout_pulse)
# signal data acquisition
exp.acquire(
signal="acquire",
handle="ac_0",
kernel=readout_weighting_function,
)
# relax time after readout - for signal processing and qubit relaxation to ground state
with exp.section(uid="relax"):
exp.delay(signal="measure", time=1e-6)
Define Signal Map and Run Experiment¶
# define signal maps for qubit 0
map_q0 = {
"drive": device_setup.logical_signal_groups["q0"].logical_signals["drive_line"],
"measure": device_setup.logical_signal_groups["q0"].logical_signals["measure_line"],
"acquire": device_setup.logical_signal_groups["q0"].logical_signals["acquire_line"],
}
exp.set_signal_map(map_q0)
# run experiment on qubit 0
my_results = session.run(exp)
# Plot simulated output signals
plot_simulation(session.compiled_experiment, 0, 10e-6)
start_beta = 0.0
stop_beta = 1
count = 5
beta_sweep = LinearSweepParameter(uid="beta", start=start, stop=stop, count=count)
Define Experiment¶
Note that the parameter beta
is unique to the drag pulse (Only the parameters length
and amplitude
are shared by all pulses).
Therefore, the parameters handled by the Experiment.play
command slightly differ from amplitude sweep above.
# Create Experiment
exp = Experiment(
uid="Amplitude Rabi",
signals=[
ExperimentSignal("drive"),
ExperimentSignal("measure"),
ExperimentSignal("acquire"),
],
)
## experimental pulse sequence
# outer loop - real-time, cyclic averaging in standard integration mode
with exp.acquire_loop_rt(
uid="shots",
count=2**5,
averaging_mode=AveragingMode.CYCLIC,
acquisition_type=AcquisitionType.INTEGRATION,
):
# inner loop - real-time sweep of qubit drive pulse drag parameter
with exp.sweep(uid="sweep", parameter=beta_sweep, alignment=SectionAlignment.RIGHT):
# qubit excitation - pulse amplitude will be swept
with exp.section(uid="qubit_excitation", alignment=SectionAlignment.RIGHT):
exp.play(signal="drive", pulse=x90, pulse_parameters={"beta": beta_sweep})
# qubit readout pulse and data acquisition
with exp.section(uid="qubit_readout"):
exp.reserve(signal="drive")
# play readout pulse
exp.play(signal="measure", pulse=readout_pulse)
# signal data acquisition
exp.acquire(
signal="acquire",
handle="ac_0",
kernel=readout_weighting_function,
)
# relax time after readout - for signal processing and qubit relaxation to ground state
with exp.section(uid="relax"):
exp.delay(signal="measure", time=1e-6)
Define Signal Map and Run the Experiment¶
# define signal maps for qubit 0
map_q0 = {
"drive": device_setup.logical_signal_groups["q0"].logical_signals["drive_line"],
"measure": device_setup.logical_signal_groups["q0"].logical_signals["measure_line"],
"acquire": device_setup.logical_signal_groups["q0"].logical_signals["acquire_line"],
}
exp.set_signal_map(map_q0)
# run experiment on qubit 0
my_results = session.run(exp)
# Plot simulated output signals
plot_simulation(session.compiled_experiment, 0, 10e-6)
Define a new Pulse Type and Sweep it¶
You can define arbitrary pulse types. All you need to do is to decorate your functional with the pulse_library.register_pulse_functional
decorator delivered with LabOne Q. Please refer to the API Documentation for more technical details.
Here, we create an example with a flat-top Gaussian pulse.
When you define your own pulses, just a few constraints exist:
- The first argument of your function is
x
, and ranges from -1 to 1. - The last keyword argument
**_
receives the pulse lengthlength
in absolute time, sampling ratesampling_rate
, and relative amplitudeamplitude
used when the pulse is actually played. The definition offlattop_gaussian_v2
further down shows how to use them. - In between, you can define an arbitrary number of your own pulses.
@pulse_library.register_pulse_functional
def flattop_gaussian(x, relative_length_flat=0.8, **_):
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
Instead of defining everything relative to the span from -1 to 1, the length argument can be used to obtain an absolute reference in the pulse definition.
For a flat-top Gaussian, this could be used to define constant, absolute rise and fall times of the pulse:
@pulse_library.register_pulse_functional
def flattop_gaussian_v2(x, slope_length=15e-9, length=..., **_):
# assume that the rising and falling Gaussian parts should have 3*sigma width
sigma = slope_length / 3
sigma_rel = sigma / length
relative_length_flat = 1 - 6 * sigma_rel # subtract 3*sigma from the left,
res = np.ones(len(x))
res[x <= -relative_length_flat] = np.exp(
-((x[x <= -relative_length_flat] + relative_length_flat) ** 2)
/ (2 * sigma_rel**2)
)
res[x >= relative_length_flat] = np.exp(
-((x[x >= relative_length_flat] - relative_length_flat) ** 2)
/ (2 * sigma_rel**2)
)
return res
flattop = flattop_gaussian(
uid="flattop", length=400e-9, amplitude=1, relative_length_flat=0.9
)
sweep_rel_flat = LinearSweepParameter(
uid="sweep_rel_flat", start=0.05, stop=0.95, count=5
)
# Create Experiment
exp = Experiment(
uid="Amplitude Rabi",
signals=[
ExperimentSignal("drive"),
ExperimentSignal("measure"),
ExperimentSignal("acquire"),
],
)
## experimental pulse sequence
# outer loop - real-time, cyclic averaging in standard integration mode
with exp.acquire_loop_rt(
uid="shots",
count=8,
averaging_mode=AveragingMode.CYCLIC,
acquisition_type=AcquisitionType.INTEGRATION,
):
# inner loop - real-time sweep of the relative length of the flat part of the drive pulse
with exp.sweep(
uid="sweep", parameter=sweep_rel_flat, alignment=SectionAlignment.RIGHT
):
# qubit excitation - pulse amplitude will be swept
with exp.section(uid="qubit_excitation", alignment=SectionAlignment.RIGHT):
exp.play(
signal="drive",
pulse=flattop,
pulse_parameters={"relative_length_flat": sweep_rel_flat},
)
# qubit readout pulse and data acquisition
with exp.section(uid="qubit_readout"):
exp.reserve(signal="drive")
# play readout pulse
exp.play(
signal="measure", pulse=readout_pulse
) # , pulse_parameters={"relative_length_flat": sweep_rel_flat})
# signal data acquisition
exp.acquire(
signal="acquire",
handle="ac_0",
kernel=readout_weighting_function,
)
# relax time after readout - for signal processing and qubit relaxation to ground state
with exp.section(uid="relax"):
exp.delay(signal="measure", time=1e-6)
# define signal maps for qubit 0
map_q0 = {
"drive": device_setup.logical_signal_groups["q0"].logical_signals["drive_line"],
"measure": device_setup.logical_signal_groups["q0"].logical_signals["measure_line"],
"acquire": device_setup.logical_signal_groups["q0"].logical_signals["acquire_line"],
}
exp.set_signal_map(map_q0)
# run experiment on qubit 0
my_results = session.run(exp)
# Plot simulated output signals
plot_simulation(session.compiled_experiment, 0, 10e-6)
Same Experiment with Absolute Timings of sigma
¶
flattop_v2 = flattop_gaussian_v2(
uid="flattop_v2", length=400e-9, amplitude=1, slope_length=15e-9
)
slope_sweep = LinearSweepParameter(uid="slope_sweep", start=5e-9, stop=180e-9, count=5)
# Create Experiment
exp = Experiment(
uid="Amplitude Rabi",
signals=[
ExperimentSignal("drive"),
ExperimentSignal("measure"),
ExperimentSignal("acquire"),
],
)
## experimental pulse sequence
# outer loop - real-time, cyclic averaging in standard integration mode
with exp.acquire_loop_rt(
uid="shots",
count=8,
averaging_mode=AveragingMode.CYCLIC,
acquisition_type=AcquisitionType.INTEGRATION,
):
# inner loop - real-time sweep of the slope sweep
with exp.sweep(
uid="sweep", parameter=slope_sweep, alignment=SectionAlignment.RIGHT
):
# qubit excitation - pulse amplitude will be swept
with exp.section(uid="qubit_excitation", alignment=SectionAlignment.RIGHT):
exp.play(
signal="drive",
pulse=flattop_v2,
pulse_parameters={"slope_length": slope_sweep},
)
# qubit readout pulse and data acquisition
with exp.section(uid="qubit_readout"):
exp.reserve(signal="drive")
# play readout pulse
exp.play(signal="measure", pulse=readout_pulse)
# signal data acquisition
exp.acquire(
signal="acquire",
handle="ac_0",
kernel=readout_weighting_function,
)
# relax time after readout - for signal processing and qubit relaxation to ground state
with exp.section(uid="relax"):
exp.delay(signal="measure", time=1e-6)
# define signal maps for qubit 0
map_q0 = {
"drive": device_setup.logical_signal_groups["q0"].logical_signals["drive_line"],
"measure": device_setup.logical_signal_groups["q0"].logical_signals["measure_line"],
"acquire": device_setup.logical_signal_groups["q0"].logical_signals["acquire_line"],
}
exp.set_signal_map(map_q0)
# run experiment on qubit 0
my_results = session.run(exp)
# Plot simulated output signals
plot_simulation(session.compiled_experiment, 0, 10e-6)
Create a Sampled Pulse from an Array of Sampling Points¶
In case you want to play a list of sampling points, you can create a pulse containing these points. The samples can either be real or complex numbers.
When you use sampled pulses, please keep two points in mind:
- If you want to use sampled pulses, you need to know the sampling rate of your system. Please refer to our documentation to learn more.
- By definition, sampled pulses are not parameterized. However, their length and phase can be swept as demonstrated in the example below.
Define a Sampled Pulse¶
import numpy as np
n_samples_real = 1024
n_samples_complex = 128
sample_list_real = np.random.randn(n_samples_real)
sample_list_complex = np.random.randn(n_samples_complex) + 1j * np.random.randn(
n_samples_complex
)
sampled_pulse_real = pulse_library.PulseSampledReal(
samples=sample_list_real, uid="real_pulse"
)
sampled_pulse_complex = pulse_library.PulseSampledComplex(
samples=sample_list_complex, uid="complex_pulse"
)
Create Experiment and Amplitude Sweep¶
# create two parameter sweeps
amplitude_sweep = LinearSweepParameter(
uid="amplitude_sweep", start=0.1, stop=1, count=5
)
phase_sweep = LinearSweepParameter(uid="phase_sweep", start=0, stop=np.pi / 2, count=5)
# Create Experiment
exp = Experiment(
uid="Amplitude Rabi",
signals=[
ExperimentSignal("drive"),
ExperimentSignal("measure"),
ExperimentSignal("acquire"),
],
)
## experimental pulse sequence
# outer loop - real-time, cyclic averaging in standard integration mode
with exp.acquire_loop_rt(
uid="shots",
count=8,
averaging_mode=AveragingMode.CYCLIC,
acquisition_type=AcquisitionType.INTEGRATION,
):
# inner loop - real-time sweep of drive amplitudes
with exp.sweep(
uid="sweep",
parameter=[phase_sweep, amplitude_sweep],
alignment=SectionAlignment.RIGHT,
):
# qubit excitation - pulse amplitude and phase will be swept
with exp.section(uid="qubit_excitation", alignment=SectionAlignment.RIGHT):
exp.play(signal="drive", pulse=sampled_pulse_real, phase=phase_sweep)
exp.delay(signal="drive", time=100e-9)
exp.play(
signal="drive", pulse=sampled_pulse_complex, amplitude=amplitude_sweep
)
# qubit readout pulse and data acquisition
with exp.section(uid="qubit_readout"):
exp.reserve(signal="drive")
# play readout pulse
exp.play(signal="measure", pulse=readout_pulse)
# signal data acquisition
exp.acquire(
signal="acquire",
handle="ac_0",
kernel=readout_weighting_function,
)
# relax time after readout - for signal processing and qubit relaxation to ground state
with exp.section(uid="relax"):
exp.delay(signal="measure", time=1e-6)
# define signal maps for qubit 0
map_q0 = {
"drive": device_setup.logical_signal_groups["q0"].logical_signals["drive_line"],
"measure": device_setup.logical_signal_groups["q0"].logical_signals["measure_line"],
"acquire": device_setup.logical_signal_groups["q0"].logical_signals["acquire_line"],
}
exp.set_signal_map(map_q0)
# run experiment on qubit 0
my_results = session.run(exp)
# Plot simulated output signals
plot_simulation(session.compiled_experiment, 0, 10e-6)