Qubit Tune-up with SHF Instruments¶
In this notebook we demonstrate qubit tuneup with the LabOne Q software, implemented as a sequence of experiments.
Before starting the experiments, we define a set of initial qubit parameters, as might be known from fabrication.
These parameters can then be used to update the baseline calibration used in the experiments.
# LabOne Q:
from laboneq.simple import *
# plotting and fitting functionality
from laboneq.analysis.fitting import (
lorentzian,
oscillatory,
oscillatory_decay,
)
from laboneq.contrib.example_helpers.plotting.plot_helpers import plot_simulation
# descriptor imports
from laboneq.contrib.example_helpers.descriptors.shfqc import descriptor_shfqc
from laboneq.contrib.example_helpers.descriptors.shfsg_shfqa_pqsc import (
descriptor_shfsg_shfqa_pqsc,
)
from laboneq.contrib.example_helpers.descriptors.shfsg_shfqa_shfqc_hdawg_pqsc import (
descriptor_shfsg_shfqa_shfqc_hdawg_pqsc,
)
# for saving results and pulse sheets
from pathlib import Path
import time
import matplotlib.pyplot as plt
import numpy as np
# Define and Load our Device Setup
device_setup = DeviceSetup.from_descriptor(
# yaml_text=descriptor_shfqc,
yaml_text=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_setup", # setup name
)
# define shortcut to logical signals for convenience
lsg = {
qubit_name: device_setup.logical_signal_groups[qubit_name].logical_signals
for qubit_name in device_setup.logical_signal_groups.keys()
}
# shfqa_address = []
# shfsg_address = []
# hdawg_address = []
# for instrument in device_setup.instruments:
# if "QA" in instrument.uid.upper():
# # print(device_setup.instruments[i].address)
# shfqa_address.append(instrument.address)
# if "SG" in instrument.uid.upper():
# # print(device_setup.instruments[i].address)
# shfsg_address.append(instrument.address)
# if "HD" in instrument.uid.upper():
# # print(device_setup.instruments[i].address)
# hdawg_address.append(instrument.address)
1.2 Qubit Parameters¶
A python dictionary containing all parameters needed to control and readout the qubits - frequencies, pulse lengths, timings
May initially contain only the design parameters and will be updated with measurement results during the tuneup procedure
# a function to define a collection of single qubit control and readout parameters as a python dictionary
def single_qubit_parameters():
return {
"freq": 100e6, # qubit 0 drive frequency in [Hz] - relative to local oscillator for qubit drive upconversion
"ro_freq": 5e6, # 50e6,
"ro_delay": 0, # 15e-9,#100e-9,
"ro_int_delay": 0, # 40-9,
"qb_len_spec": 1e-6,
"qb_len": 700e-9,
"qb_amp_spec": 1.0,
"pi_amp": 1,
"freq_ef": -500e6,
}
# for sake of simplicity, give all qubits the same initial parameters
qubit_parameters = {
k: single_qubit_parameters() for k in device_setup.logical_signal_groups.keys()
}
# up / downconversion settings - to convert between IF and RF frequencies
def single_lo_settings():
return {
# SHFQA LO Frequency
"shfqa_lo": 8.0e9,
# SHFSG LO Frequencies, one center frequency per two channels on SHFQC
"shfsg_lo": 5.0e9,
}
lo_settings = {
k: single_lo_settings() for k in device_setup.logical_signal_groups.keys()
}
1.3 Setup Calibration¶
Generate a calibration object from the qubit control and readout parameters
# function that defines the device settings for qubit and readout parameters
def define_calibration(device_setup, parameters, lo_settings):
# Define LOs
def single_oscillator(id, qubit, lo_type):
oscillator = Oscillator()
oscillator.uid = f"{id}" + f"{qubit}" + "_osc"
oscillator.frequency = lo_settings[qubit][lo_type]
return oscillator
readout_lo_dict = {
k: single_oscillator("readout_lo_", k, "shfqa_lo")
for k in device_setup.logical_signal_groups.keys()
}
drive_lo_dict = {
k: single_oscillator("drive_lo_", k, "shfsg_lo")
for k in device_setup.logical_signal_groups.keys()
}
# the calibration object will later be applied to the device setup
calibration = Calibration()
# qubits q0-q3 are multiplexed on one acquisition line
calibration[
device_setup.logical_signal_groups["q0"].logical_signals["acquire_line"]
] = SignalCalibration(
oscillator=Oscillator(
frequency=parameters["q0"]["ro_freq"],
modulation_type=ModulationType.SOFTWARE,
),
# add an offset between the readout pulse and the start of the data acquisition - to compensate for round-trip time of readout pulse
port_delay=parameters["q0"]["ro_delay"] + parameters["q0"]["ro_int_delay"],
local_oscillator=readout_lo_dict["q0"],
range=5,
)
for logical_signal_group in device_setup.logical_signal_groups.keys():
# measure line
calibration[
device_setup.logical_signal_groups[logical_signal_group].logical_signals[
"measure_line"
]
] = SignalCalibration(
oscillator=Oscillator(
frequency=parameters[logical_signal_group]["ro_freq"],
modulation_type=ModulationType.SOFTWARE,
),
port_delay=parameters["q0"]["ro_delay"],
local_oscillator=readout_lo_dict["q0"],
range=5,
)
calibration[
device_setup.logical_signal_groups[logical_signal_group].logical_signals[
"drive_line"
]
] = SignalCalibration(
# each logical signal can have an oscillator associated with it
oscillator=Oscillator(
frequency=parameters[logical_signal_group]["freq"],
modulation_type=ModulationType.HARDWARE,
),
# DANGER! Verify which qubits share the same LOs!
local_oscillator=drive_lo_dict[logical_signal_group],
range=5,
)
return calibration
# define Calibration object based on qubit control and readout parameters
calibration = define_calibration(device_setup, qubit_parameters, lo_settings)
# apply calibration to device setup
device_setup.set_calibration(calibration)
2.2 Create and Connect to a QCCS Session¶
Establishes the connection to the instruments and readies them for experiments
# 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. Qubit Tuneup - Experimental Sequence¶
Sequence of experiments for tuneup from scratch of a superconducting qubit in circuit QED architecture
3.1 Resonator Spectroscopy: CW¶
Find the resonance frequency of the qubit readout resonator by looking at the transmission or reflection of a probe signal applied through the readout line
3.1.1 Additional Experimental Parameters¶
Define the frequency scan
# frequency range of spectroscopy scan - around expected centre frequency as defined in qubit parameters
start_freq = -500.0e6
stop_freq = 500.0e6
num_points = 501
integration_time = 1e-3
num_averages = 2
# define sweep parameter
def create_readout_freq_sweep(qubit, start_freq, stop_freq, num_points):
return LinearSweepParameter(
uid=f"res_freq_{qubit}",
start=start_freq + qubit_parameters[qubit]["ro_freq"],
stop=stop_freq + qubit_parameters[qubit]["ro_freq"],
count=num_points,
)
3.1.2 Experiment Definition¶
Define the experimental pulse and readout sequence - here without any explicit qubit reference
Explicit qubit reference is then given through different experimental calibration and signal maps
# function that defines a resonator spectroscopy experiment, and takes the frequency sweep as a parameter
def res_spectroscopy_CW(freq_sweep, exp_settings):
# Create resonator spectroscopy experiment - uses only readout drive and signal acquisition
exp_spec = Experiment(
uid="Resonator Spectroscopy",
signals=[
ExperimentSignal("measure"),
ExperimentSignal("acquire"),
],
)
## define experimental sequence
# loop - average multiple measurements for each frequency - measurement in spectroscopy mode
with exp_spec.acquire_loop_rt(
uid="shots",
count=exp_settings["num_averages"],
acquisition_type=AcquisitionType.SPECTROSCOPY,
):
with exp_spec.sweep(uid="res_freq", parameter=freq_sweep):
# readout pulse and data acquisition
with exp_spec.section(uid="spectroscopy"):
# resonator signal readout
exp_spec.acquire(
signal="acquire",
handle="res_spec",
length=exp_settings["integration_time"],
)
with exp_spec.section(uid="delay", length=1e-6):
# holdoff time after signal acquisition
exp_spec.reserve(signal="measure")
return exp_spec
# function that returns the calibration of the readout line oscillator for the experimental signals
def res_spec_calib(frequency_sweep, amplitude_sweep=None):
exp_calibration = Calibration()
# sets the oscillator of the experimental measure signal
exp_calibration["measure"] = SignalCalibration(
# for spectroscopy, use the hardware oscillator of the QA, and set the sweep parameter as frequency
oscillator=Oscillator(
"readout_osc",
frequency=frequency_sweep,
modulation_type=ModulationType.HARDWARE,
),
amplitude=amplitude_sweep,
)
return exp_calibration
# signal maps for the two different qubits - maps the logical signal of the device setup to the experimental signals of the experiment
def res_spec_map(qubit):
signal_map = {
"measure": device_setup.logical_signal_groups[f"{qubit}"].logical_signals[
"measure_line"
],
"acquire": device_setup.logical_signal_groups[f"{qubit}"].logical_signals[
"acquire_line"
],
}
return signal_map
3.1.3 Run and Evaluate Experiment¶
Runs the experiment and evaluates the data returned by the measurement
# define the experiment with the frequency sweep relevant for qubit 0
freq_sweep = create_readout_freq_sweep("q0", start_freq, stop_freq, num_points)
exp_settings = {"integration_time": integration_time, "num_averages": num_averages}
exp_spec = res_spectroscopy_CW(freq_sweep, exp_settings)
# set signal calibration and signal map for experiment to qubit 0
exp_spec.set_calibration(res_spec_calib(freq_sweep))
exp_spec.set_signal_map(res_spec_map("q0"))
# run the experiment on the open instrument session
compiled_res_spec = session.compile(exp_spec)
res_spec_results = session.run()
# get the measurement data returned by the instruments from the QCCS session
spec_res = res_spec_results.get_data("res_spec")
# define the frequency axis from the qubit parameters
spec_freq = lo_settings["q0"]["shfqa_lo"] + res_spec_results.get_axis("res_spec")[0]
if emulate:
# create some dummy data if running in emulation mode
spec_res = lorentzian(
spec_freq,
10e6,
qubit_parameters["q0"]["ro_freq"] * (0.995 + 0.01 * np.random.rand(1)[0])
+ lo_settings["q0"]["shfqa_lo"],
-1e7,
10,
) + 0.2 * np.random.rand(len(spec_freq))
# plot the measurement data
fig, [ax1, ax2] = plt.subplots(2, 1)
ax1.plot(spec_freq / 1e9, abs(spec_res), ".k")
ax2.plot(spec_freq / 1e9, np.unwrap(np.angle(spec_res)), "orange")
ax1.set_ylabel("A (a.u.)")
ax2.set_ylabel("$\\phi$ (rad)")
ax2.set_xlabel("Frequency (GHz)")
plt.show()
(p_opt, b) = lorentzian.fit(
spec_freq,
-abs(spec_res),
50e6,
lo_settings["q0"]["shfqa_lo"],
-1e7,
10,
plot=True,
)
opt_freq = p_opt[1]
print(f"Resonant frequency: {opt_freq} GHz")
3.1.4 Update Calibration¶
if not emulate:
# update qubit parameter dictionary with results from data fitting
qubit_parameters["q0"]["ro_freq"] = opt_freq - lo_settings["q0"]["shfqa_lo"]
# update calibration
# apply calibration to device setup
device_setup.set_calibration(
define_calibration(device_setup, qubit_parameters, lo_settings)
)
3.2 Resonator Spectroscopy: Pulsed¶
3.2.1 Additional Experimental Parameters¶
Define the frequency scan and the pulse
# frequency range of spectroscopy scan -
# around expected centre frequency as defined in qubit parameters
start_freq = -500e6
stop_freq = 500e6
num_points = 1001
# define sweep parameter
def create_readout_freq_sweep(qubit, start_freq, stop_freq, num_points):
return LinearSweepParameter(
uid=f"{qubit}_res_freq",
start=start_freq,
stop=stop_freq,
count=num_points,
axis_name="Frequency [Hz]",
)
# define number of averages
# used for 2^num_averages, maximum: num_averages = 17
num_averages = 4
# pulse parameters and definitions
envelope_duration = 2.0e-6
sigma = 0.2
flat_duration = 1.0e-6
def create_readout_pulse(
qubit, length=envelope_duration, amplitude=0.9, width=flat_duration, sigma=sigma
):
readout_pulse = pulse_library.gaussian_square(
uid=f"readout_pulse_{qubit}",
length=length,
amplitude=amplitude,
width=width,
sigma=sigma,
)
return readout_pulse
readout_pulse = create_readout_pulse("q0")
3.2.2 Experiment Definition¶
# function that defines a resonator spectroscopy experiment, and takes the frequency sweep as a parameter
def res_spectroscopy_pulsed(freq_sweep, num_averages, readout_pulse):
# Create resonator spectroscopy experiment - uses only readout drive and signal acquisition
exp_spec_pulsed = Experiment(
uid="Resonator Spectroscopy",
signals=[
ExperimentSignal("measure"),
ExperimentSignal("acquire"),
],
)
## define experimental sequence
# outer loop - vary drive frequency
# inner loop - average multiple measurements for each frequency - measurement in spectroscopy mode
with exp_spec_pulsed.acquire_loop_rt(
uid="shots",
count=2**num_averages,
acquisition_type=AcquisitionType.SPECTROSCOPY,
averaging_mode=AveragingMode.SEQUENTIAL,
):
with exp_spec_pulsed.sweep(
uid="res_freq",
parameter=freq_sweep,
chunk_count=1,
):
# readout pulse and data acquisition
with exp_spec_pulsed.section(uid="spectroscopy"):
# play resonator excitation pulse
exp_spec_pulsed.play(signal="measure", pulse=readout_pulse)
# resonator signal readout
exp_spec_pulsed.acquire(
signal="acquire", handle="res_spec_pulsed", length=envelope_duration
)
with exp_spec_pulsed.section(uid="delay", length=1e-6):
# holdoff time after signal acquisition - minimum 1us required for data processing on UHFQA
exp_spec_pulsed.reserve(signal="measure")
return exp_spec_pulsed
3.2.3 Apply Experiment Parameters and Compile¶
# create freq sweep
freq_sweep = create_readout_freq_sweep("q0", start_freq, stop_freq, num_points)
# define the experiment with the frequency sweep relevant for qubit 0
exp_spec_pulsed = res_spectroscopy_pulsed(freq_sweep, num_averages, readout_pulse)
# set signal calibration and signal map for experiment to qubit 0
exp_spec_pulsed.set_calibration(res_spec_calib(freq_sweep))
exp_spec_pulsed.set_signal_map(res_spec_map("q0"))
# compile the experiment on the open instrument session
compiled_spec_pulsed = session.compile(exp_spec_pulsed)
Path("Pulse_Sheets").mkdir(parents=True, exist_ok=True)
# generate a pulse sheet to inspect experiment before runtime
show_pulse_sheet("Pulse_Sheets/Pulsed_Spectroscopy", compiled_spec_pulsed)
3.2.4 Run and Evaluate Experiment¶
# run the experiment on the open instrument session
spec_pulsed_results = session.run()
# get the measurement data returned by the instruments from the QCCS session
spec_res = spec_pulsed_results.get_data("res_spec_pulsed")
# define the frequency axis from the qubit parameters
spec_freq = (
lo_settings["q0"]["shfqa_lo"] + spec_pulsed_results.get_axis("res_spec_pulsed")[0]
)
if emulate:
# create some dummy data if running in emulation mode
spec_res = lorentzian(
spec_freq,
10e6,
qubit_parameters["q0"]["ro_freq"] * (0.995 + 0.01 * np.random.rand(1)[0])
+ lo_settings["q0"]["shfqa_lo"],
-2e6,
1,
) + 0.1 * np.random.rand(len(spec_freq))
# plot the measurement data
fig, [ax1, ax2] = plt.subplots(2, 1)
ax1.plot(spec_freq / 1e9, abs(spec_res), ".k")
ax2.plot(spec_freq / 1e9, np.unwrap(np.angle(spec_res)), "orange")
ax1.set_ylabel("A (a.u.)")
ax2.set_ylabel("$\\phi$ (rad)")
ax2.set_xlabel("Frequency (GHz)")
3.2.5 Update calibration¶
Extract the resonance frequency and update the calibration
if not emulate:
(p_opt, b) = lorentzian.fit(
spec_freq,
-abs(spec_res),
10e6,
lo_settings["q0"]["shfqa_lo"],
-1e5,
1,
plot=True,
)
opt_freq = p_opt[1]
print(f"Resonant frequency: {opt_freq} GHz")
qubit_parameters["q0"]["ro_freq"] = opt_freq - lo_settings["q0"]["shfqa_lo"]
device_setup.set_calibration(
define_calibration(device_setup, qubit_parameters, lo_settings)
)
3.3 Resonator Spectroscopy v/ Power¶
- It is possible to define the spectroscopy experiments in a more general way, so that CW and pulsed spectroscopy can be chosen with an argument to the function. However, this is not done here for the sake of clarity
- One of the next releases will allow for real-time frequency sweep on the SHFQA, which will make the experiment much faster
3.3.1 Additional experimental parameters¶
# frequency range of spectroscopy scan -
# around expected centre frequency as defined in qubit parameters
start_freq = -500e6
stop_freq = 500e6
num_freq_points = 1001
# set number of points for amplitude sweep
num_amp_points = 21
3.3.2 Experiment Definition¶
# function that defines a resonator spectroscopy experiment, and takes the frequency sweep as a parameter
def res_spectroscopy_pulsed_amp_sweep(
frequency_sweep, amplitude_sweep, num_averages, readout_pulse
):
# Create resonator spectroscopy experiment - uses only readout drive and signal acquisition
exp_spec = Experiment(
uid="Resonator Spectroscopy",
signals=[
ExperimentSignal("measure"),
ExperimentSignal("acquire"),
],
)
## define experimental sequence
# outer loop - vary drive amplitude
with exp_spec.sweep(uid="res_amp", parameter=amplitude_sweep):
with exp_spec.acquire_loop_rt(
uid="shots",
count=2**num_averages,
acquisition_type=AcquisitionType.SPECTROSCOPY,
):
with exp_spec.sweep(uid="res_freq", parameter=frequency_sweep):
# readout pulse and data acquisition
with exp_spec.section(uid="spectroscopy"):
# play resonator excitation pulse
exp_spec.play(signal="measure", pulse=readout_pulse)
# resonator signal readout
exp_spec.acquire(
signal="acquire",
handle="res_spec_pulsed",
length=envelope_duration,
)
with exp_spec.section(uid="delay"):
# holdoff time after signal acquisition - minimum 1us required for data processing on UHFQA
exp_spec.delay(signal="measure", time=1e-6)
return exp_spec
3.3.3 Apply Experiment Parameters and Compile¶
amplitude_sweep = LinearSweepParameter(
uid="amp_sweep_param", start=0.1, stop=0.99, count=num_amp_points
)
frequency_sweep = create_readout_freq_sweep(
"q0", start_freq, stop_freq, num_freq_points
)
exp_spec_amp = res_spectroscopy_pulsed_amp_sweep(
frequency_sweep, amplitude_sweep, num_averages, readout_pulse
)
exp_spec_amp.set_calibration(res_spec_calib(frequency_sweep, amplitude_sweep))
exp_spec_amp.set_signal_map(res_spec_map("q0"))
# compile the experiment on the open instrument session
compiled_spec_amp = session.compile(exp_spec_amp)
Path("Pulse_Sheets").mkdir(parents=True, exist_ok=True)
# generate a pulse sheet to inspect experiment before runtime
show_pulse_sheet(
"Pulse_Sheets/Spectroscopy_vs_Amplitude_Pulse_Sheet", compiled_spec_amp
)
# run the compiled experiemnt
spec_amp_results = session.run(compiled_spec_amp)
timestamp = time.strftime("%Y%m%dT%H%M%S")
Path("Results").mkdir(parents=True, exist_ok=True)
session.save_results(f"Results/{timestamp}_spec_amp_results.json")
print(f"File saved as Results/{timestamp}_spec_amp_results.json")
3.3.4 Run and Evaluate Experiment¶
spec_freq = (
lo_settings["q0"]["shfqa_lo"] + spec_amp_results.get_axis("res_spec_pulsed")[1]
)
amp = spec_amp_results.get_axis("res_spec_pulsed")[0]
data = spec_amp_results.get_data("res_spec_pulsed")
# plot the results
for key in spec_amp_results.acquired_results.keys():
lo_freq = spec_amp_results.device_calibration.calibration_items[
"/logical_signal_groups/q0/measure_line"
].local_oscillator.frequency
spec_freq = lo_freq + spec_amp_results.get_axis(f"{key}")[1]
amp = spec_amp_results.get_axis(f"{key}")[0]
data = spec_amp_results.get_data(f"{key}")
X, Y = np.meshgrid(spec_freq, amp)
fig, ax = plt.subplots(nrows=1, ncols=2, constrained_layout=True)
CS = ax[0].contourf(X / 1e9, Y, np.abs(data), levels=100, cmap="magma")
ax[0].set_title("Readout amplitude")
ax[0].set_xlabel("Frequency [GHz]")
ax[0].set_ylabel("Normalized amplitude")
fig.colorbar(CS)
max_value = (
max(
int(np.abs(np.min(np.unwrap(np.angle(data, deg=True))))),
int(np.abs(np.max(np.unwrap(np.angle(data, deg=True))))),
)
+ 1
)
cs2_levels = np.linspace(-max_value, max_value, 2 * (max_value) + 1)
CS2 = ax[1].contourf(
X / 1e9,
Y,
np.unwrap(np.angle(data, deg=True)),
levels=cs2_levels,
cmap="twilight_shifted",
)
ax[1].set_title("Phase")
ax[1].set_xlabel("Frequency [GHz]")
ax[1].set_ylabel("Normalized amplitude")
fig.colorbar(CS2)
# define delay sweep
delay_sweep = LinearSweepParameter(
uid="delay_sweep_param", start=0, stop=1.0e-6, count=21
)
# define number of averages
# used for 2^num_averages, maximum: num_averages = 17
num_averages = 4
3.4.2 Experiment Definition¶
# function that defines a resonator spectroscopy experiment, and takes the frequency sweep as a parameter
def propagation_delay(readout_pulse, delay_sweep):
# Create resonator spectroscopy experiment - uses only readout drive and signal acquisition
exp_prop_delay = Experiment(
uid="Propagation Delay Measurement",
signals=[
ExperimentSignal("measure"),
ExperimentSignal("acquire"),
],
)
## define experimental sequence
# outer loop - vary drive frequency
with exp_prop_delay.sweep(uid="del_sweep", parameter=delay_sweep):
with exp_prop_delay.acquire_loop_rt(
uid="shots",
count=2**num_averages,
acquisition_type=AcquisitionType.INTEGRATION,
):
# readout pulse and data acquisition
with exp_prop_delay.section(uid="spectroscopy"):
# play resonator excitation pulse
exp_prop_delay.play(signal="measure", pulse=readout_pulse)
# resonator signal readout
exp_prop_delay.acquire(
signal="acquire", handle="res_prop_delay", kernel=readout_pulse
)
with exp_prop_delay.section(uid="delay"):
# holdoff time after signal acquisition - minimum 1us required for data processing on UHFQA
exp_prop_delay.delay(signal="measure", time=1e-6)
cal = Calibration()
cal["measure"] = SignalCalibration(
port_delay=delay_sweep,
)
return exp_prop_delay
3.4.3 Apply Experiment Parameters and Compile¶
short_readout_pulse = create_readout_pulse("q0", length=600e-9, width=200e-9, sigma=0.2)
device_setup.set_calibration(
define_calibration(device_setup, qubit_parameters, lo_settings)
)
exp_prop_delay = propagation_delay(short_readout_pulse, delay_sweep)
exp_prop_delay.set_signal_map(res_spec_map("q0"))
# compile the experiment on the open instrument session
compiled_prop_delay = session.compile(exp_prop_delay)
Path("Pulse_Sheets").mkdir(parents=True, exist_ok=True)
# generate a pulse sheet to inspect experiment before runtime
show_pulse_sheet("Pulse_Sheets/Propagation_delay", compiled_prop_delay)
3.4.4 Run and Evaluate Experiment¶
# run the compiled experiemnt
prop_delay_results = session.run(compiled_prop_delay)
timestamp = time.strftime("%Y%m%dT%H%M%S")
Path("Results").mkdir(parents=True, exist_ok=True)
session.save_results(f"Results/{timestamp}_prop_delay_results.json")
print(f"File saved as Results/{timestamp}_prop_delay_results.json")
dat = prop_delay_results.get_data("res_prop_delay")
ax = prop_delay_results.get_axis("res_prop_delay")[0]
dat = prop_delay_results.get_data("res_prop_delay")
plt.plot(ax * 1e9, np.abs(dat))
plt.xlabel("Integration delay [ns]")
plt.ylabel("Integration result")
plt.show()
3.5 Pulsed Qubit Spectroscopy¶
Find the resonance frequency of the qubit by looking at the change in resonator transmission when sweeping the frequency of a qubit excitation pulse
3.5.1 Additional Experimental Parameters¶
Define the frequency scan and the pulses used in the experiment
def create_drive_freq_sweep(qubit, start_freq, stop_freq, num_points):
return LinearSweepParameter(
uid=f"drive_freq_{qubit}",
start=start_freq + qubit_parameters[qubit]["freq"],
stop=stop_freq + qubit_parameters[qubit]["freq"],
count=num_points,
)
def create_drive_spec_pulse(qubit):
pulse = pulse_library.const(
uid=f"drive_spec_pulse_{qubit}",
length=qubit_parameters[qubit]["qb_len_spec"],
amplitude=qubit_parameters[qubit]["qb_amp_spec"],
)
return pulse
# frequency range of spectroscopy scan - defined around expected qubit frequency as defined in qubit parameters
qspec_range = 100e6
# how many frequency points to measure
qspec_num = 1001
# define number of averages
# used for 2^num_averages, maximum: num_averages = 17
num_averages = 10
3.5.2 Experiment Definition¶
The frequency sweep of the drive line can now be done in real time (was: near time in older software releases)
# function that returns a qubit spectroscopy experiment- accepts frequency sweep range as parameter
def qubit_spectroscopy(freq_sweep, drive_pulse, readout_pulse):
# Create qubit spectroscopy Experiment - uses qubit drive, readout drive and data acquisition lines
exp_qspec = Experiment(
uid="Qubit Spectroscopy",
signals=[
ExperimentSignal("drive"),
ExperimentSignal("measure"),
ExperimentSignal("acquire"),
],
)
# inner loop - real-time averaging - QA in integration mode
with exp_qspec.acquire_loop_rt(
uid="freq_shots",
count=2**num_averages,
acquisition_type=AcquisitionType.INTEGRATION,
):
with exp_qspec.sweep(uid="qfreq_sweep", parameter=freq_sweep):
# qubit drive
with exp_qspec.section(uid="qubit_excitation"):
# exp_qspec.play(signal="drive", pulse=drive_pulse)
exp_qspec.play(signal="drive", pulse=drive_pulse)
with exp_qspec.section(
uid="readout_section", play_after="qubit_excitation"
):
# play readout pulse on measure line
exp_qspec.play(signal="measure", pulse=readout_pulse)
# trigger signal data acquisition
exp_qspec.acquire(
signal="acquire",
handle="qb_spec",
kernel=readout_pulse,
)
with exp_qspec.section(uid="delay"):
# relax time after readout - for qubit relaxation to groundstate and signal processing
exp_qspec.delay(signal="measure", time=1e-6)
return exp_qspec
freq_sweep_q0 = create_drive_freq_sweep("q0", -qspec_range, qspec_range, qspec_num)
freq_sweep_q1 = create_drive_freq_sweep("q1", -qspec_range, qspec_range, qspec_num)
# experiment signal calibration for qubit 0
exp_calibration_q0 = Calibration()
exp_calibration_q0["drive"] = SignalCalibration(
oscillator=Oscillator(
frequency=freq_sweep_q0,
modulation_type=ModulationType.HARDWARE,
),
)
# signal map for qubit 0
def signal_map_default(qubit):
signal_map = {
"drive": device_setup.logical_signal_groups[f"{qubit}"].logical_signals[
"drive_line"
],
"measure": device_setup.logical_signal_groups[f"{qubit}"].logical_signals[
"measure_line"
],
"acquire": device_setup.logical_signal_groups[f"{qubit}"].logical_signals[
"acquire_line"
],
}
return signal_map
3.5.3 Run and Evaluate Experiment for Both Qubits¶
Runs the experiment and evaluates the data returned by the measurement
# define experiment with frequency sweep for qubit 0
drive_pulse = create_drive_spec_pulse("q0")
readout_pulse = create_readout_pulse("q0", length=1000e-9, width=900e-9, sigma=0.2)
device_setup.set_calibration(
define_calibration(device_setup, qubit_parameters, lo_settings)
)
exp_qspec = qubit_spectroscopy(freq_sweep_q0, drive_pulse, readout_pulse)
# apply calibration and signal map for qubit 0
exp_qspec.set_calibration(exp_calibration_q0)
exp_qspec.set_signal_map(signal_map_default("q0"))
# compile the experiment on the open instrument session
compiled_qspec = session.compile(exp_qspec)
Path("Pulse_Sheets").mkdir(parents=True, exist_ok=True)
# generate a pulse sheet to inspect experiment before runtime
show_pulse_sheet("Pulse_Sheets/Qubit_Spectroscopy", compiled_qspec)
plot_simulation(compiled_qspec, 0, 100e-6)
# run the experiment on qubit 0
qspec_results = session.run()
# get measurement data returned by the instruments
qspec_res = qspec_results.get_data("qb_spec")
qspec_freq = qspec_results.get_axis("qb_spec")[0] + lo_settings["q0"]["shfsg_lo"]
if emulate:
# create some dummy data if running in emulation mode
qspec_res = lorentzian(
qspec_freq,
5e6,
qubit_parameters["q0"]["freq"] * (0.995 + 0.01 * np.random.rand(1)[0])
+ lo_settings["q0"]["shfsg_lo"],
-2e6,
1,
) + 0.1 * np.random.rand(len(qspec_freq))
# plot measurement data
fig = plt.figure()
plt.plot(qspec_freq / 1e9, abs(qspec_res), ".k")
plt.ylabel("A (a.u.)")
plt.xlabel("Frequency (GHz)")
plt.show()
Do fitting and frequency update now if not in emulation mode:
# update qubit parameters
if not emulate:
# increase number of plot points for smooth plotting of fit reults
freq_plot = np.linspace(qspec_freq[0], qspec_freq[-1], 5 * len(qspec_freq))
# fit measurement data - here assuming an inverted Lorentzian response
popt, pcov = lorentzian.fit(
qspec_freq,
abs(qspec_res),
5e6,
qubit_parameters["q0"]["freq"] + lo_settings["q0"]["shfsg_lo"],
-1e6,
1,
plot=False,
bounds=[[0, 4e9, -1e8, 0], [10e6, 6e9, 0, 2]],
)
print(f"Fitted parameters: {popt}")
# plot fit results together with measurement data
plt.plot(freq_plot / 1e9, lorentzian(freq_plot, *popt), "-r")
qubit_parameters["q0"]["freq"] = popt[1] - lo_settings["q0"]["shfsg_lo"]
qubit_parameters["q0"]["freq"]
3.6 Amplitude Rabi Experiment¶
Sweep the pulse amplitude of a qubit drive pulse to determine the ideal amplitudes for specific qubit rotation angles
3.6.1 Additional Experimental Parameters¶
Define the amplitude sweep range and qubit excitation pulse
# range of pulse amplitude scan
def create_rabi_amp_sweep(qubit, amp_num, uid="rabi_amp"):
amp_min = 0
amp_max = min([qubit_parameters[qubit]["pi_amp"] * 2.2, 1.0])
return LinearSweepParameter(uid=uid, start=amp_min, stop=amp_max, count=amp_num)
# define number of averages
# used for 2^num_averages, maximum: num_averages = 17
num_averages = 10
def create_rabi_drive_pulse(qubit):
return pulse_library.gaussian(
uid=f"gaussian_drive_q{qubit}",
length=qubit_parameters[qubit]["qb_len"],
amplitude=1,
)
# qubit drive frequency - defined in calibration on device setup as baseline reference
lsg["q0"]["drive_line"].oscillator.frequency = qubit_parameters["q0"]["freq"]
# set oscillator type to hardware to ensure optimal use of the instrument functionality
lsg["q0"]["drive_line"].oscillator.modulation_type = ModulationType.HARDWARE
3.6.2 Experiment Definition¶
Define the experimental pulse and readout sequence - here without any explicit qubit reference
Explicit qubit reference is then given through different experimental calibration and signal maps
# function that returns an amplitude Rabi experiment
def amplitude_rabi(drive_pulse, readout_pulse, amplitude_sweep):
exp_rabi = Experiment(
uid="Amplitude Rabi",
signals=[
ExperimentSignal("drive"),
ExperimentSignal("measure"),
ExperimentSignal("acquire"),
],
)
## define Rabi experiment pulse sequence
# outer loop - real-time, cyclic averaging
with exp_rabi.acquire_loop_rt(
uid="rabi_shots",
count=2**num_averages,
averaging_mode=AveragingMode.CYCLIC,
acquisition_type=AcquisitionType.INTEGRATION,
):
# inner loop - real time sweep of Rabi ampitudes
with exp_rabi.sweep(uid="rabi_sweep", parameter=amplitude_sweep):
# play qubit excitation pulse - pulse amplitude is swept
with exp_rabi.section(
uid="qubit_excitation", alignment=SectionAlignment.RIGHT
):
exp_rabi.play(
signal="drive", pulse=drive_pulse, amplitude=amplitude_sweep
)
# readout pulse and data acquisition
with exp_rabi.section(uid="readout_section", play_after="qubit_excitation"):
# play readout pulse on measure line
exp_rabi.play(signal="measure", pulse=readout_pulse)
# trigger signal data acquisition
exp_rabi.acquire(
signal="acquire",
handle="amp_rabi",
kernel=readout_pulse,
)
with exp_rabi.section(uid="delay", length=1e-6):
# relax time after readout - for qubit relaxation to groundstate and signal processing
exp_rabi.reserve(signal="measure")
return exp_rabi
3.6.3 Set Experiment Parameters and Compile¶
# set signal map for qubit 0 - no experimental calibration necessary, calibration taken from DeviceSetup, i.e. baseline
device_setup.set_calibration(
define_calibration(device_setup, qubit_parameters, lo_settings)
)
drive_pulse = create_rabi_drive_pulse("q0")
exp_rabi = amplitude_rabi(
drive_pulse, readout_pulse, create_rabi_amp_sweep(qubit="q0", amp_num=61)
)
exp_rabi.set_signal_map(signal_map_default("q0"))
# compile the experiment on the open instrument session
compiled_rabi = session.compile(exp_rabi)
3.6.4 Show Pulse Sheet¶
show_pulse_sheet("Pulse_Sheets/Amplitude_Rabi", compiled_rabi)
3.6.5 Plot Simulated Outputs¶
# Simulate experiment
plot_simulation(compiled_rabi, 0, 100e-6)
3.6.6 Run, Save, and Plot Results¶
Finally, you'll run the experiment, save, and plot the results.
# run the compiled experiemnt
rabi_results = session.run(compiled_rabi)
timestamp = time.strftime("%Y%m%dT%H%M%S")
Path("Results").mkdir(parents=True, exist_ok=True)
session.save_results(f"Results/{timestamp}_rabi_results.json")
print(f"File saved as Results/{timestamp}_rabi_results.json")
# get measurement data returned by the instruments
rabi_res = rabi_results.get_data("amp_rabi")
# define amplitude axis from qubit parameters
rabi_amp = rabi_results.get_axis("amp_rabi")[0]
if emulate:
# create some dummy data if running in emulation mode
rabi_res = oscillatory(rabi_amp, 10, 0, 1, 1.2) + 0.2 * np.random.rand(
len(rabi_amp)
)
# plot measurement data
fig = plt.figure()
plt.plot(rabi_amp, rabi_res, ".k")
plt.ylabel("A (a.u.)")
plt.xlabel("amplitude (a.u.)")
# increase number of plot points for smooth plotting of fit results
amp_plot = np.linspace(rabi_amp[0], rabi_amp[-1], 5 * len(rabi_amp))
# fit measurement results - assume sinusoidal oscillation with drive amplitude
popt, pcov = oscillatory.fit(rabi_amp, rabi_res, 10, 0, 1, 1.2, plot=False)
print(f"Fitted parameters: {popt}")
# plot fit results together with measurement data
plt.plot(amp_plot, oscillatory(amp_plot, *popt), "-r");
3.7 Ramsey Experiment¶
The Ramsey experiment is different from the experiments above as the length of the drive section changes. Using a right-aligned sweep section and the automatic repetition time makes sure that the experiment is run as efficiently as possible on the Zurich Instruments hardware.
3.7.1 Experiment Parameters¶
# define number of averages
# used for 2^num_averages, maximum: num_averages = 17
num_averages = 4
# define delay sweep
n_steps = 201
start_delay = 0e-6
stop_delay = 15e-6
def create_delay_sweep(
start=start_delay, stop=stop_delay, count=n_steps, axis_name="Time [s]"
):
time_sweep = LinearSweepParameter(
uid="time_sweep_param", start=start, stop=stop, count=count, axis_name=axis_name
)
return time_sweep
# define ramsey drive pulse
def create_ramsey_drive_pulse(qubit):
return pulse_library.gaussian(
uid=f"gaussian_drive_q{qubit}",
length=qubit_parameters[qubit]["qb_len"],
amplitude=0.5,
)
3.7.2 Experiment Definition¶
# function that returns an amplitude Rabi experiment
def ramsey(drive_pulse, readout_pulse, time_sweep):
exp_ramsey = Experiment(
uid="Ramsey Experiment",
signals=[
ExperimentSignal("drive"),
ExperimentSignal("measure"),
ExperimentSignal("acquire"),
],
)
## define Rabi experiment pulse sequence
# outer loop - real-time, cyclic averaging
with exp_ramsey.acquire_loop_rt(
uid="ramsey_shots",
count=2**num_averages,
averaging_mode=AveragingMode.CYCLIC,
acquisition_type=AcquisitionType.INTEGRATION,
repetition_mode=RepetitionMode.AUTO,
):
# inner loop - real time sweep of Ramsey time delays
with exp_ramsey.sweep(
uid="ramsey_sweep", parameter=time_sweep, alignment=SectionAlignment.RIGHT
):
# play qubit excitation pulse - pulse amplitude is swept
with exp_ramsey.section(
uid="qubit_excitation", alignment=SectionAlignment.RIGHT
):
exp_ramsey.play(signal="drive", pulse=drive_pulse)
exp_ramsey.delay(signal="drive", time=time_sweep)
exp_ramsey.play(signal="drive", pulse=drive_pulse)
# readout pulse and data acquisition
with exp_ramsey.section(
uid="readout_section", play_after="qubit_excitation"
):
# play readout pulse on measure line
exp_ramsey.play(signal="measure", pulse=readout_pulse)
# trigger signal data acquisition
exp_ramsey.acquire(
signal="acquire",
handle="ramsey",
kernel=readout_pulse,
)
with exp_ramsey.section(uid="delay", length=1e-6):
# relax time after readout - for qubit relaxation to groundstate and signal processing
exp_ramsey.reserve(signal="measure")
return exp_ramsey
3.7.3 Create Experiment and Signal Map¶
# define pulses and create experiment
readout_pulse = create_readout_pulse("q0")
drive_pulse = create_ramsey_drive_pulse("q0")
time_sweep = create_delay_sweep(start=start_delay, stop=stop_delay, count=n_steps)
ramsey_exp = ramsey(
drive_pulse=drive_pulse, readout_pulse=readout_pulse, time_sweep=time_sweep
)
ramsey_exp.set_signal_map(signal_map_default("q0"))
compiled_ramsey = session.compile(ramsey_exp)
3.7.4 Show Pulse Sheet¶
show_pulse_sheet("Pulse_Sheets/Ramsey", compiled_ramsey)
3.7.5 Plot Simulated Outputs¶
plot_simulation(compiled_ramsey, 0e-6, 100e-6, plot_width=10)
3.7.6 Run, Save, and Plot Results¶
# run the compiled experiemnt
ramsey_results = session.run()
timestamp = time.strftime("%Y%m%dT%H%M%S")
Path("Results").mkdir(parents=True, exist_ok=True)
session.save_results(f"Results/{timestamp}_ramsey_results.json")
print(f"File saved as Results/{timestamp}_ramsey_results.json")
# get measurement data returned by the instruments
ramsey_res = ramsey_results.get_data("ramsey")
# define time axis from qubit parameters
ramsey_delay = ramsey_results.get_axis("ramsey")[0]
if emulate:
# create dummy data if running in emulation mode
ramsey_res = oscillatory_decay(
ramsey_delay, 1e6, 0, 1 / 10e-6, amplitude=0.5, offset=0.5
) + 0.12 * np.random.rand(len(ramsey_delay))
# plot measurement results
fig = plt.figure()
plt.plot(ramsey_delay, ramsey_res, ".k")
plt.ylabel("A (a.u.)")
plt.xlabel("delay (s)")
# increase number of plot points for smooth plotting of fit results
delay_plot = np.linspace(ramsey_delay[0], ramsey_delay[-1], 5 * len(ramsey_delay))
## fit measurement data to decaying sinusoidal oscillatio
popt, pcov = oscillatory_decay.fit(
ramsey_delay,
ramsey_res,
1e6,
0,
2 / 1 / 10e-6,
0.5,
0.5,
plot=False,
bounds=[
[0.01e6, -np.pi / 2, 0.1 / 1 / 10e-6, 0.2, 0.2],
[15e6, np.pi / 2, 10 / 1 / 10e-6, 2, 2],
],
)
print(f"Fitted parameters: {popt}")
# plot fit results together with experimental data
plt.plot(delay_plot, oscillatory_decay(delay_plot, *popt), "-r");