Single Qubit Tuneup with UHFQA and HDAWG¶
In this notebook we demonstrate single 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.
Stepping through the experiments, starting with resonator spectroscopy, then qubit spectroscopy, amplitude Rabi and then T1 and Ramsey experiments, these parameters are succesively updated with the ones determined from the measurement data.
# LabOne Q:
from laboneq.simple import *
# Helpers:
from laboneq.analysis.fitting import (
exponential_decay,
lorentzian,
oscillatory,
oscillatory_decay,
)
from laboneq.contrib.example_helpers.plotting.plot_helpers import plot_simulation
import matplotlib.pyplot as plt
import numpy as np
my_descriptor = """\
instruments:
HDAWG:
- address: DEV8001
uid: device_hdawg
UHFQA:
- address: DEV2001
uid: device_uhfqa
PQSC:
- address: DEV10001
uid: device_pqsc
connections:
device_hdawg:
- iq_signal: q0/drive_line
ports: [SIGOUTS/0, SIGOUTS/1]
- iq_signal: q1/drive_line
ports: [SIGOUTS/2, SIGOUTS/3]
- rf_signal: q0/flux_line
ports: [SIGOUTS/4]
- rf_signal: q1/flux_line
ports: [SIGOUTS/5]
- to: device_uhfqa
port: DIOS/0
device_uhfqa:
- iq_signal: q0/measure_line
ports: [SIGOUTS/0, SIGOUTS/1]
- acquire_signal: q0/acquire_line
- iq_signal: q1/measure_line
ports: [SIGOUTS/0, SIGOUTS/1]
- acquire_signal: q1/acquire_line
device_pqsc:
- to: device_hdawg
port: ZSYNCS/0
"""
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 collection of qubit control and readout parameters as a python dictionary
qubit_parameters = {
"ro_freq_q0": 50e6, # readout frequency of qubit 0 in [Hz] - relative to local oscillator for readout drive upconversion
"ro_freq_q1": 100e6, # readout frequency of qubit 0 in [Hz] - relative to local oscillator for readout drive upconversion
"ro_amp": 1.0, # readout amplitude
"ro_amp_spec": 1.0, # readout amplitude for spectroscopy
"ro_len": 2.1e-6, # readout pulse length in [s]
"ro_len_spec": 15e-6, # readout pulse length for resonator spectroscopy in [s]
"ro_delay": 100e-9, # readout delay after last drive signal in [s]
"ro_int_delay": 40e-9, # readout line offset calibration - delay between readout pulse and start of signal acquisition in [s]
"qb0_freq": 100e6, # qubit 0 drive frequency in [Hz] - relative to local oscillator for qubit drive upconversion
"qb1_freq": 50e6, # qubit 1 drive frequency in [Hz] - relative to local oscillator for qubit drive upconversion
"qb_amp_spec": 1.0, # drive amplitude of qubit spectroscopy
"qb_len_spec": 15e-6, # drive pulse length for qubit spectroscopy in [s]
"qb_len": 20e-9, # qubit drive pulse length in [s]
"pi_amp": 1.0, # qubit drive amplitude for pi pulse
"pihalf_amp": 0.5, # qubit drive amplitude for pi/2 pulse
"ramsey_det": 1e6, # detuning applied to drive for Ramsey experiment in [Hz]
"T1": 10e-6, # qubit lifetime T1 in [s]
"T2_ramsey": 10e-6, # qubit dephasing time T2 in [s]
"T2_echo": 25e-6, # qubit echo dephasing time T2_E in [s]
"relax": 1e-6, # delay time after each measurement for qubit reset (minimum time 1us - signal acquisition and processing delay of UHFQA) in [s]
}
# up / downconversion settings - to convert between IF and RF frequencies
lo_settings = {
"qb_lo": 5.3e9, # qubit LO frequency in [Hz]
"ro_lo": 6.4e9, # readout LO frequency in [Hz]
}
1.3 Setup Calibration¶
Generate a calibration object from the qubit control and readout parameters
# function that defines a setup calibration containing the qubit / readout parameters
def define_calibration(parameters):
# the calibration object will later be applied to the device setup
my_calibration = Calibration()
## Calibration information for qubit 0
# qubit drive line - the calibration object contains SignalCalibration entries for each logical signal
my_calibration["/logical_signal_groups/q0/drive_line"] = SignalCalibration(
# each logical signal can have an oscillator associated with it
oscillator=Oscillator(
"q0_drive_osc",
frequency=parameters["qb0_freq"],
modulation_type=ModulationType.HARDWARE,
),
# each logical signal may contain mixer calibration settings
mixer_calibration=MixerCalibration(
voltage_offsets=[0.0, 0.0],
correction_matrix=[
[1.0, 0.0],
[0.0, 1.0],
],
),
)
# readout drive line
my_calibration["/logical_signal_groups/q0/measure_line"] = SignalCalibration(
oscillator=Oscillator(
"q0_measure_osc",
frequency=parameters["ro_freq_q0"],
modulation_type=ModulationType.SOFTWARE,
),
# use delay_signal on measure line to ensure compatibility with UHFQA
delay_signal=parameters["ro_delay"],
)
# acquisition line
my_calibration["/logical_signal_groups/q0/acquire_line"] = SignalCalibration(
oscillator=Oscillator(
"q0_acquire_osc",
frequency=parameters["ro_freq_q0"],
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["ro_delay"] + parameters["ro_int_delay"],
)
## Calibration information for qubit 1
# qubit drive line
my_calibration["/logical_signal_groups/q1/drive_line"] = SignalCalibration(
oscillator=Oscillator(
"q1_drive_osc",
frequency=parameters["qb1_freq"],
modulation_type=ModulationType.HARDWARE,
),
mixer_calibration=MixerCalibration(
voltage_offsets=[0.0, 0.0],
correction_matrix=[
[1.0, 0.0],
[0.0, 1.0],
],
),
)
# readout pulse line
my_calibration["/logical_signal_groups/q1/measure_line"] = SignalCalibration(
oscillator=Oscillator(
"q1_measure_osc",
frequency=parameters["ro_freq_q1"],
modulation_type=ModulationType.SOFTWARE,
),
# use delay_signal on measure line to ensure compatibility with UHFQA
delay_signal=parameters["ro_delay"],
)
# acquisition line
my_calibration["/logical_signal_groups/q1/acquire_line"] = SignalCalibration(
oscillator=Oscillator(
"q1_acquire_osc",
frequency=parameters["ro_freq_q1"],
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["ro_delay"] + parameters["ro_int_delay"],
)
return my_calibration
# define the DeviceSetup from descriptor - additionally include information on the dataserver used to connect to the instruments
my_setup = DeviceSetup.from_descriptor(
my_descriptor,
server_host="my_dataserver_ip",
server_port="8004",
setup_name="my_qccs_setup",
)
# define Calibration object based on qubit control and readout parameters
my_calibration = define_calibration(parameters=qubit_parameters)
# apply calibration to device setup
my_setup.set_calibration(my_calibration)
## define shortcut to logical signals for convenience
lsg_q0 = my_setup.logical_signal_groups["q0"].logical_signals
drive_Oscillator_q0 = lsg_q0["drive_line"].oscillator
readout_Oscillator_q0 = lsg_q0["measure_line"].oscillator
acquire_Oscillator_q0 = lsg_q0["acquire_line"].oscillator
lsg_q1 = my_setup.logical_signal_groups["q1"].logical_signals
drive_Oscillator_q1 = lsg_q1["drive_line"].oscillator
readout_Oscillator_q1 = lsg_q1["measure_line"].oscillator
acquire_Oscillator_q1 = lsg_q1["acquire_line"].oscillator
2.2 Create and Connect to a LabOne Q 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
my_session = Session(device_setup=my_setup)
my_session.connect(do_emulation=emulate)
3. Single Qubit Tuneup - Experimental Sequence¶
Sequence of experiments for tuneup from scratch of a superconducting qubit in circuit QED architecture
3.1 Resonator Spectroscopy¶
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 and the excitation pulse
# frequency range of spectroscopy scan - around expected centre frequency as defined in qubit parameters
spec_range = 200e6
# how many frequency points to measure
spec_num = 50
# define sweep parameters for two qubits
freq_sweep_q0 = LinearSweepParameter(
uid="res_freq",
start=qubit_parameters["ro_freq_q0"] - spec_range / 2,
stop=qubit_parameters["ro_freq_q0"] + spec_range / 2,
count=spec_num,
)
freq_sweep_q1 = LinearSweepParameter(
uid="res_freq",
start=qubit_parameters["ro_freq_q1"] - spec_range / 2,
stop=qubit_parameters["ro_freq_q1"] + spec_range / 2,
count=spec_num,
)
# take how many averages per point: 2^n_average
n_average = 10
# spectroscopy excitation pulse
readout_pulse_spec = pulse_library.const(
length=qubit_parameters["ro_len_spec"], amplitude=qubit_parameters["ro_amp_spec"]
)
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(freq_sweep):
# 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 frequency
with exp_spec.sweep(uid="res_freq", parameter=freq_sweep):
# inner loop - average multiple measurements for each frequency - measurement in spectroscopy mode
with exp_spec.acquire_loop_rt(
uid="shots",
count=pow(2, n_average),
acquisition_type=AcquisitionType.SPECTROSCOPY,
):
# readout pulse and data acquisition
with exp_spec.section(uid="spectroscopy"):
# play resonator excitation pulse
exp_spec.play(signal="measure", pulse=readout_pulse_spec)
# resonator signal readout
exp_spec.acquire(
signal="acquire",
handle="res_spec",
length=qubit_parameters["ro_len_spec"],
)
with exp_spec.section(uid="delay", play_after="spectroscopy"):
# holdoff time after signal acquisition - minimum 1us required for data processing on UHFQA
exp_spec.delay(signal="measure", time=1e-6)
return exp_spec
# function that returns the calibration of the readout line oscillator for the experimental signals
def res_spec_calib(freq_sweep):
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=freq_sweep, modulation_type=ModulationType.HARDWARE
)
)
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
res_spec_map_q0 = {
"measure": "/logical_signal_groups/q0/measure_line",
"acquire": "/logical_signal_groups/q0/acquire_line",
}
res_spec_map_q1 = {
"measure": "/logical_signal_groups/q1/measure_line",
"acquire": "/logical_signal_groups/q1/acquire_line",
}
3.1.3 Run and Evaluate Experiment for Both Qubits¶
Runs the experiment and evaluates the data returned by the measurement
- for qubit 0¶
# define the experiment with the frequency sweep relevant for qubit 0
exp_spec = res_spectroscopy(freq_sweep_q0)
# set signal calibration and signal map for experiment to qubit 0
exp_spec.set_calibration(res_spec_calib(freq_sweep_q0))
exp_spec.set_signal_map(res_spec_map_q0)
# run the experiment on the open instrument session
my_results = my_session.run(exp_spec)
# get the measurement data returned by the instruments from the LabOne Q session
spec_res = my_results.get_data("res_spec")
# define the frequency axis from the qubit parameters
spec_freq = lo_settings["ro_lo"] + my_results.get_axis("res_spec")[0]
if emulate:
# create some dummy data if running in emulation mode
spec_res = lorentzian(
spec_freq,
5e6,
qubit_parameters["ro_freq_q0"] * (0.995 + 0.01 * np.random.rand(1)[0])
+ lo_settings["ro_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(r"$\phi$ (rad)")
ax2.set_xlabel("Frequency (GHz)")
# increase number of plot points for smooth plotting of fit results
freq_plot = np.linspace(spec_freq[0], spec_freq[-1], 5 * len(spec_freq))
# fit the measurement data to extract the resonance frequency - here assuming a Lorentzian resonance lineshape
popt, pcov = lorentzian.fit(
spec_freq,
abs(spec_res),
5e6,
qubit_parameters["ro_freq_q0"] + lo_settings["ro_lo"],
-1e7,
10,
plot=False,
bounds=[[0, 6e9, -1e8, -0.1], [10e6, 7e9, 0, 20]],
)
print(f"Fitted parameters: {popt}")
# plot the fit results together with the measurement data
ax1.plot(freq_plot / 1e9, lorentzian(freq_plot, *popt), "-r");
# update qubit parameter dictionary with results from data fitting
qubit_parameters["ro_freq_q0"] = popt[1] - lo_settings["ro_lo"]
qubit_parameters["ro_freq_q0"]
- for qubit 1¶
# define the experiment with the frequency sweep relevant for qubit 1
exp_spec = res_spectroscopy(freq_sweep_q1)
# set signal calibration and signal map for experiment to qubit 0
exp_spec.set_calibration(res_spec_calib(freq_sweep_q1))
exp_spec.set_signal_map(res_spec_map_q1)
# run the experiment on the open instrument session
my_results = my_session.run(exp_spec)
# get the measurement data returned by the instruments from the LabOne Q session
spec_res = my_results.get_data("res_spec")
# define the frequency axis from the qubit parameters
spec_freq = lo_settings["ro_lo"] + my_results.get_axis("res_spec")[0]
if emulate:
# create some dummy data if running in emulation mode
spec_res = lorentzian(
spec_freq,
5e6,
qubit_parameters["ro_freq_q1"] * (0.995 + 0.01 * np.random.rand(1)[0])
+ lo_settings["ro_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(r"$\phi$ (rad)")
ax2.set_xlabel("Frequency (GHz)")
# increase number of plot points for smooth plotting of fit results
freq_plot = np.linspace(spec_freq[0], spec_freq[-1], 5 * len(spec_freq))
# fit the measurement data to extract the resonance frequency - here assuming a Lorentzian resonance lineshape
popt, pcov = lorentzian.fit(
spec_freq,
abs(spec_res),
5e6,
qubit_parameters["ro_freq_q1"] + lo_settings["ro_lo"],
-1e7,
10,
plot=False,
bounds=[[0, 6.0e9, -1e8, -0.1], [10e6, 7e9, 0, 20]],
)
print(f"Fitted parameters: {popt}")
# plot the fit results together with the measurement data
ax1.plot(freq_plot / 1e9, lorentzian(freq_plot, *popt), "-r");
# update qubit parameter dictionary with results from data fitting
qubit_parameters["ro_freq_q1"] = popt[1] - lo_settings["ro_lo"]
qubit_parameters["ro_freq_q1"]
3.2 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.2.1 Additional Experimental Parameters¶
Define the frequency scan and the pulses used in the experiment
# 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 = 51
# set up sweep parameters - qubit drive frequency
freq_sweep_q0 = LinearSweepParameter(
uid="freq-qubit",
start=qubit_parameters["qb0_freq"] - qspec_range / 2,
stop=qubit_parameters["qb0_freq"] + qspec_range / 2,
count=qspec_num,
)
freq_sweep_q1 = LinearSweepParameter(
uid="freq-qubit",
start=qubit_parameters["qb1_freq"] - qspec_range / 2,
stop=qubit_parameters["qb1_freq"] + qspec_range / 2,
count=qspec_num,
)
# how many averages per point: 2^n_average
n_average = 10
# square pulse to excite the qubit
square_pulse = pulse_library.const(
uid="const_iq",
length=qubit_parameters["qb_len_spec"],
amplitude=qubit_parameters["qb_amp_spec"],
)
# qubit readout pulse - here simple constant pulse
readout_pulse = pulse_library.const(
uid="readout_pulse",
length=qubit_parameters["ro_len"],
amplitude=qubit_parameters["ro_amp"],
)
# integration weights for qubit measurement - here simple constant weights, i.e. all parts of the return signal are weighted equally
readout_weighting_function = pulse_library.const(
uid="readout_weighting_function", length=qubit_parameters["ro_len"], amplitude=1.0
)
3.2.2 Readout Pulse and Signal Acquisition¶
Define a reusable code section containing the readout pulse and signal acquisition statements - will be used in all subsequent experiments
# function that modifies an experiment by adding the qubit readout and signal acquisition sections
def readoutQubit(
exp,
section_id="qubit_readout",
readout_id="q0_measure",
acquire_id="q0_acquire",
acquire_handle="q0_acquire",
reserve_id=None,
readout_pulse=readout_pulse,
readout_weights=readout_weighting_function,
relax_time=1e-6,
):
# section for readout pulse and data acquisition
with exp.section(uid=section_id):
# reserve drive line for duration of readout and data acquisition - if selected
if reserve_id is not None:
exp.reserve(signal=reserve_id)
# play readout pulse on measure line
exp.play(signal=readout_id, pulse=readout_pulse)
# trigger signal data acquisition
exp.acquire(
signal=acquire_id,
handle=acquire_handle,
kernel=readout_weights,
)
with exp.section(uid="delay", play_after=section_id):
# relax time after readout - for qubit relaxation to groundstate and signal processing
exp.delay(signal=readout_id, time=relax_time)
# make sure that the drive line is reserved also for the relax time, if selected
if reserve_id is not None:
exp.reserve(signal=reserve_id)
3.2.3 Change Calibration of Readout and Acquire Line¶
Apply the readout frequency parameter as measured in the resonator spectroscopy experiment
## configure oscillator settings for readout and acquisition - defined as calibration on device setup as persistent baseline settings
# readout pulse frequency resonant with the readout resonator
readout_Oscillator_q0.frequency = qubit_parameters["ro_freq_q0"]
# change oscillator type to sfotware to enable multiplexed qubit readout
readout_Oscillator_q0.modulation_type = ModulationType.SOFTWARE
# demodulation frequency same as readout pulse frequency
acquire_Oscillator_q0.frequency = qubit_parameters["ro_freq_q0"]
acquire_Oscillator_q0.modulation_type = ModulationType.SOFTWARE
readout_Oscillator_q1.frequency = qubit_parameters["ro_freq_q1"]
readout_Oscillator_q1.modulation_type = ModulationType.SOFTWARE
acquire_Oscillator_q1.frequency = qubit_parameters["ro_freq_q1"]
acquire_Oscillator_q1.modulation_type = ModulationType.SOFTWARE
3.2.4 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 a qubit spectroscopy experiment- accepts frequency sweep range as parameter
def qubit_spectroscopy(freq_sweep):
# 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"),
],
)
## define experimental pulse sequence
# outer loop - near time qubit drive frequency sweep
with exp_qspec.sweep(
uid="qfreq_sweep", parameter=freq_sweep, execution_type=ExecutionType.NEAR_TIME
):
# inner loop - real-time averaging - QA in integration mode
with exp_qspec.acquire_loop_rt(
uid="qfreq_shots",
count=pow(2, n_average),
acquisition_type=AcquisitionType.INTEGRATION,
):
# qubit drive
with exp_qspec.section(uid="qubit_excitation"):
exp_qspec.play(signal="drive", pulse=square_pulse)
# readout pulse and data acquisition - use code section defined earlier
readoutQubit(
exp_qspec,
section_id="qubit_readout",
readout_id="measure",
acquire_id="acquire",
reserve_id="drive",
acquire_handle="q0_spec",
readout_pulse=readout_pulse,
readout_weights=readout_weighting_function,
relax_time=qubit_parameters["relax"],
)
return exp_qspec
# 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
q0_map = {
"drive": "/logical_signal_groups/q0/drive_line",
"measure": "/logical_signal_groups/q0/measure_line",
"acquire": "/logical_signal_groups/q0/acquire_line",
}
# experiment signal calibration for qubit 1
exp_calibration_q1 = Calibration()
exp_calibration_q1["drive"] = SignalCalibration(
oscillator=Oscillator(
frequency=freq_sweep_q1,
modulation_type=ModulationType.HARDWARE,
),
)
# signal map for qubit 1
q1_map = {
"drive": "/logical_signal_groups/q1/drive_line",
"measure": "/logical_signal_groups/q1/measure_line",
"acquire": "/logical_signal_groups/q1/acquire_line",
}
3.2.5 Run and Evaluate Experiment for Both Qubits¶
Runs the experiment and evaluates the data returned by the measurement
- for qubit 0¶
# define experiment with frequency sweep for qubit 0
exp_qspec = qubit_spectroscopy(freq_sweep_q0)
# apply calibration and signal map for qubit 0
exp_qspec.set_calibration(exp_calibration_q0)
exp_qspec.set_signal_map(q0_map)
# run the experiment on qubit 0
my_results = my_session.run(exp_qspec)
# Plot simulated output signals
plot_simulation(my_session.compiled_experiment, 0, 40e-6)
# get measurement data returned by the instruments
qspec_res = my_results.get_data("q0_spec")
# define a frequency axis from the parameters
qspec_freq = lo_settings["qb_lo"] + my_results.get_axis("q0_spec")[0]
if emulate:
# create some dummy data if running in emulation mode
qspec_res = lorentzian(
qspec_freq,
5e6,
qubit_parameters["qb0_freq"] * (0.995 + 0.01 * np.random.rand(1)[0])
+ lo_settings["qb_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)")
# 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["qb0_freq"] + lo_settings["qb_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");
# update qubit parameters
qubit_parameters["qb0_freq"] = popt[1] - lo_settings["qb_lo"]
qubit_parameters["qb0_freq"]
- for qubit 1¶
# define experiment with frequency sweep for qubit 1
exp_qspec = qubit_spectroscopy(freq_sweep_q1)
# apply calibration and signal map for qubit 1
exp_qspec.set_calibration(exp_calibration_q1)
exp_qspec.set_signal_map(q1_map)
# run the experiment on qubit 1
my_results = my_session.run(exp_qspec)
# get measurement data returned by the instruments
qspec_res = my_results.get_data("q0_spec")
# define a frequency axis from the parameters
qspec_freq = lo_settings["qb_lo"] + my_results.get_axis("q0_spec")[0]
if emulate:
# create some dummy data if running in emulation mode
qspec_res = lorentzian(
qspec_freq,
5e6,
qubit_parameters["qb1_freq"] * (0.995 + 0.01 * np.random.rand(1)[0])
+ lo_settings["qb_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)")
# 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["qb1_freq"] + lo_settings["qb_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");
# update qubit parameters
qubit_parameters["qb1_freq"] = popt[1] - lo_settings["qb_lo"]
qubit_parameters["qb1_freq"]
3.3 Amplitude Rabi Experiment¶
Sweep the pulse amplitude of a qubit drive pulse to determine the ideal amplitudes for specific qubit rotation angles
3.3.1 Additional Experimental Parameters¶
Define the amplitude sweep range and qubit excitation pulse
# range of pulse amplitude scan
amp_min = 0
amp_max = min([qubit_parameters["pi_amp"] * 2.2, 1.0])
# how many amplitude points to measure
amp_num = 101
# set up sweep parameter - qubit drive pulse amplitude
rabi_sweep = LinearSweepParameter(
uid="rabi_amp", start=amp_min, stop=amp_max, count=amp_num
)
# how many averages per point: 2^n_average
n_average = 10
# Rabi excitation pulse - gaussian of unit amplitude - amplitude will be scaled with sweep parameter in experiment
gaussian_pulse = pulse_library.gaussian(
uid="gaussian_pulse", length=qubit_parameters["qb_len"], amplitude=1.0
)
3.3.2 Change Calibration of Qubit Drive Line¶
Apply the qubit resonance frequency, as found in the qubit spectroscopy experiment
# qubit drive frequency - defined in calibration on device setup as baseline reference
drive_Oscillator_q0.frequency = qubit_parameters["qb0_freq"]
# set oscillator type to hardware to ensure optimal use of the instrument functionality
drive_Oscillator_q0.modulation_type = ModulationType.HARDWARE
drive_Oscillator_q1.frequency = qubit_parameters["qb1_freq"]
drive_Oscillator_q1.modulation_type = ModulationType.HARDWARE
3.3.3 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
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=pow(2, n_average),
averaging_mode=AveragingMode.CYCLIC,
acquisition_type=AcquisitionType.INTEGRATION,
):
# inner loop - real time sweep of Rabi ampitudes
with exp_rabi.sweep(uid="rabi_sweep", parameter=rabi_sweep):
# play qubit excitation pulse - pulse amplitude is swept
with exp_rabi.section(uid="qubit_excitation"):
exp_rabi.play(signal="drive", pulse=gaussian_pulse, amplitude=rabi_sweep)
# readout pulse and data acquisition
readoutQubit(
exp_rabi,
section_id="qubit_readout",
readout_id="measure",
acquire_id="acquire",
reserve_id="drive",
acquire_handle="q0_rabi",
readout_pulse=readout_pulse,
readout_weights=readout_weighting_function,
relax_time=qubit_parameters["relax"],
)
# signal maps for both qubits - map experimental signals to logical signals
q0_map = {
"drive": lsg_q0["drive_line"],
"measure": lsg_q0["measure_line"],
"acquire": lsg_q0["acquire_line"],
}
q1_map = {
"drive": lsg_q1["drive_line"],
"measure": lsg_q1["measure_line"],
"acquire": lsg_q1["acquire_line"],
}
3.3.4 Run Experiments and Evaluate Measurement Results¶
- run for qubit 0¶
# set signal map for qubit 0 - no experimental calibration necessary, calibration taken from DeviceSetup, i.e. baseline
exp_rabi.set_signal_map(q0_map)
# run the experiment on qubit 0
my_results = my_session.run(exp_rabi)
# Plot simulated output signals
plot_simulation(my_session.compiled_experiment, 0, 40e-6)
- run for qubit 1¶
# set signal map for qubit 1 - no experimental calibration necessary, calibration taken from DeviceSetup, i.e. baseline
exp_rabi.set_signal_map(q1_map)
# run the experiment on qubit 1
my_results = my_session.run(exp_rabi)
- plot and evaluate measurement results¶
# get measurement data returned by the instruments
rabi_res = my_results.get_data("q0_rabi")
# define amplitude axis from qubit parameters
rabi_amp = my_results.get_axis("q0_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");
# update qubit parameters - pulse amplitdues for pi and pi/2 pulses
qubit_parameters["pi_amp"] = np.pi / (popt[0])
print(qubit_parameters["pi_amp"])
qubit_parameters["pihalf_amp"] = np.pi / 2 / (popt[0])
qubit_parameters["pihalf_amp"]
3.4. T1 Experiment¶
Sweep the delay between a qubit excitation pulse and the readout to measure the energy releaxation time of the qubit
3.4.1 Additional Experimental Parameters¶
# delay range for ramsey pulses
t1_min = 0.0
t1_max = 5 * qubit_parameters["T1"]
# how many delay points to sweep
t1_num = 60
# set up sweep parameter
t1_sweep = LinearSweepParameter(uid="t1_delay", start=t1_min, stop=t1_max, count=t1_num)
# how many averages per point: 2^n_average
n_average = 10
# T1 excitation pulse - qubit pi pulse
x180 = pulse_library.gaussian(
uid="x180", length=qubit_parameters["qb_len"], amplitude=qubit_parameters["pi_amp"]
)
3.4.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
# Create T1 Experiment - uses qubit drive line, readout drive line and acquisition line
exp_t1 = Experiment(
uid="Qubit T1",
signals=[
ExperimentSignal("drive"),
ExperimentSignal("measure"),
ExperimentSignal("acquire"),
],
)
## define experiment sequence
# outer loop - real-time, cyclic averaging
with exp_t1.acquire_loop_rt(
uid="t1_shots",
count=pow(2, n_average),
averaging_mode=AveragingMode.CYCLIC,
acquisition_type=AcquisitionType.INTEGRATION,
):
# inner loop - real-time sweep of delay between pi-pulse and readout
with exp_t1.sweep(uid="t1_sweep", parameter=t1_sweep):
# qubit pi pulse and following delay - use right aligned section of constant length for optimised timing behavior
with exp_t1.section(
uid="qubit_excitation",
length=t1_max + 2 * x180.length,
alignment=SectionAlignment.RIGHT,
):
exp_t1.play(signal="drive", pulse=x180)
exp_t1.delay(signal="drive", time=t1_sweep)
# readout pulse and data acquisition
readoutQubit(
exp_t1,
section_id="qubit_readout",
readout_id="measure",
acquire_id="acquire",
reserve_id="drive",
acquire_handle="q0_t1",
readout_pulse=readout_pulse,
readout_weights=readout_weighting_function,
relax_time=qubit_parameters["relax"],
)
3.4.3 Run and Evaluate Experiment for Both Qubits¶
- run for qubit 0¶
# set signal map for qubit 0 - no other calibration necessary, taken from DeviceSetup / baseline
exp_t1.set_signal_map(q0_map)
# run the experiment on qubit 0
my_results = my_session.run(exp_t1)
# Plot simulated output signals
plot_simulation(my_session.compiled_experiment, 50e-6, 1e-6)
- run for qubit 1¶
# set signal map for qubit 1 - no other calibration necessary, taken from DeviceSetup / baseline
exp_t1.set_signal_map(q1_map)
# run the experiment on qubit 1
my_results = my_session.run(exp_t1)
- plot and evaluate measurement results¶
# get measurement data returned by the instruments
t1_res = my_results.get_data("q0_t1")
# define time axis from qubit parameters
t1_delay = my_results.get_axis("q0_t1")[0]
if emulate:
# create dummy data if running in emulation mode
t1_res = exponential_decay(
t1_delay, 2 / qubit_parameters["T1"], 0.1, 1
) + 0.1 * np.random.rand(len(t1_delay))
# plot measurement results
fig = plt.figure()
plt.plot(t1_delay, t1_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(t1_delay[0], t1_delay[-1], 5 * len(t1_delay))
# fit measurement results to decaying exponential curve
popt, pcov = exponential_decay.fit(t1_delay, t1_res, 1e6, 0, plot=False)
print(f"Fitted parameters: {popt}")
# plot fit results together with measurement data
plt.plot(delay_plot, exponential_decay(delay_plot, *popt), "-r");
# update qubit parameters - here relaxation time / qubit lifetime
qubit_parameters["T1"] = 1 / popt[0]
qubit_parameters["T1"]
3.5 Ramsey Experiment¶
Sweep the delay between two slightly detuned pi/2 pulses to determine the qubit dephasing time as well as fine calibration its excited state frequency
3.5.1 Additional Experimental Parameters¶
# delay range for ramsey pulses
ramsey_min = 0.0
ramsey_max = 2.5 * qubit_parameters["T2_ramsey"]
# how many delay points to sweep
ramsey_num = 150
# set up delay sweep parameter
ramsey_sweep = LinearSweepParameter(
uid="ramsey_delay", start=ramsey_min, stop=ramsey_max, count=ramsey_num
)
# how many averages per point: 2^n_average
n_average = 10
# Ramsey excitation pulse - qubit pi/2 pulse - will be slightly detuned
x90 = pulse_library.gaussian(
uid="x90",
length=qubit_parameters["qb_len"],
amplitude=qubit_parameters["pihalf_amp"],
)
3.5.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
# Create Ramsey Experiment - uses qubit drive line, readout drive line and acquisition line
exp_ramsey = Experiment(
uid="Ramsey",
signals=[
ExperimentSignal("drive"),
ExperimentSignal("measure"),
ExperimentSignal("acquire"),
],
)
## define Experiment sequence
# outer loop - real-time, cyclic averaging
with exp_ramsey.acquire_loop_rt(
uid="ramsey_shots",
count=pow(2, n_average),
averaging_mode=AveragingMode.CYCLIC,
acquisition_type=AcquisitionType.INTEGRATION,
):
# inner loop - sweep Ramsey delay in real time
with exp_ramsey.sweep(uid="ramsey_sweep", parameter=ramsey_sweep):
# Ramsey pulse sequence on qubit - use right-aligned, constant length section for optimised timing behavior
with exp_ramsey.section(
uid="qubit_excitation",
length=ramsey_max + 2 * x90.length,
alignment=SectionAlignment.RIGHT,
):
exp_ramsey.play(signal="drive", pulse=x90)
exp_ramsey.delay(signal="drive", time=ramsey_sweep)
exp_ramsey.play(signal="drive", pulse=x90)
# readout pulse and data acquisition
readoutQubit(
exp_ramsey,
section_id="qubit_readout",
readout_id="measure",
acquire_id="acquire",
reserve_id="drive",
acquire_handle="q0_ramsey",
readout_pulse=readout_pulse,
readout_weights=readout_weighting_function,
relax_time=qubit_parameters["relax"],
)
# Calibration for qubit 0 - additional Ramsey detuning applied to drive line to induce Ramsey oscillations
ramsey_cal_0 = Calibration()
ramsey_cal_0["drive"] = SignalCalibration(
oscillator=Oscillator(
frequency=qubit_parameters["qb0_freq"] + qubit_parameters["ramsey_det"],
modulation_type=ModulationType.HARDWARE,
)
)
# Calibration for qubit 1 - additional Ramsey detuning applied to drive line to induce Ramsey oscillations
ramsey_cal_1 = Calibration()
ramsey_cal_1["drive"] = SignalCalibration(
oscillator=Oscillator(
frequency=qubit_parameters["qb1_freq"] + 2 * qubit_parameters["ramsey_det"],
modulation_type=ModulationType.HARDWARE,
)
)
3.5.3 Run and Evaluate Experiment for Both Qubits¶
- run for qubit 0¶
# set calibration and signal map for qubit 0
exp_ramsey.set_calibration(ramsey_cal_0)
exp_ramsey.set_signal_map(q0_map)
# run experiment on qubit 0
my_results = my_session.run(exp_ramsey)
# Plot simulated output signals
plot_simulation(my_session.compiled_experiment, 26e-6, 2e-6)
- run for qubit 1¶
# set calibration and signal map for qubit 1
exp_ramsey.set_calibration(ramsey_cal_1)
exp_ramsey.set_signal_map(q1_map)
# run experiment on qubit 1
my_results = my_session.run(exp_ramsey)
- plot and evaluate measurements results¶
# get measurement data returned by the instruments
ramsey_res = my_results.get_data("q0_ramsey")
# define time axis from qubit parameters
ramsey_delay = my_results.get_axis("q0_ramsey")[0]
if emulate:
# create dummy data if running in emulation mode
ramsey_res = oscillatory_decay(
ramsey_delay,
qubit_parameters["ramsey_det"],
0,
1 / qubit_parameters["T2_ramsey"],
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,
qubit_parameters["ramsey_det"],
0,
2 / qubit_parameters["T2_ramsey"],
0.5,
0.5,
plot=False,
bounds=[
[0.01e6, -np.pi / 2, 0.1 / qubit_parameters["T2_ramsey"], 0.2, 0.2],
[15e6, np.pi / 2, 10 / qubit_parameters["T2_ramsey"], 2, 2],
],
)
print(f"Fitted parameters: {popt}")
# plot fit results together with experimental data
plt.plot(delay_plot, oscillatory_decay(delay_plot, *popt), "-r");
# update qubit parameters - here qubit dephasing time
qubit_parameters["T2_ramsey"] = 1 / popt[2]
qubit_parameters["T2_ramsey"]
3.6 Hahn Echo Experiment¶
Sweep the delay between two pi/2 pulses and a pi pulse inserted at exactly half time between them, in order to determine the qubit dephasing time without noise components that are constant on the sequence time scale - gets rid of 1/f noise contributions
3.6.1 Additional Experimental Parameters¶
# delay range for echo pulses
echo_min = 0.0
echo_max = 1.5 * qubit_parameters["T2_echo"]
# how many delay points to sweep
echo_num = 150
# set up delay sweep parameter - max of sweep parameter is half of max delay since here are two delays in sequence
echo_sweep = LinearSweepParameter(
uid="echo_delay", start=echo_min, stop=echo_max / 2, count=echo_num
)
# how many averages per point: 2^n_average
n_average = 10
# Echo excitation pulses - qubit pi/2 and pi pulse
x90 = pulse_library.gaussian(
uid="x90",
length=qubit_parameters["qb_len"],
amplitude=qubit_parameters["pihalf_amp"],
)
x180 = pulse_library.gaussian(
uid="x180", length=qubit_parameters["qb_len"], amplitude=qubit_parameters["pi_amp"]
)
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
# Create Ramsey Experiment - uses qubit drive line, readout drive line and acquisition line
exp_echo = Experiment(
uid="Echo",
signals=[
ExperimentSignal("drive"),
ExperimentSignal("measure"),
ExperimentSignal("acquire"),
],
)
## define Experiment sequence
# outer loop - real-time, cyclic averaging
with exp_echo.acquire_loop_rt(
uid="echo_shots",
count=pow(2, n_average),
averaging_mode=AveragingMode.CYCLIC,
acquisition_type=AcquisitionType.INTEGRATION,
):
# inner loop - sweep Ramsey delay in real time
with exp_echo.sweep(uid="echo_sweep", parameter=echo_sweep):
# Ramsey pulse sequence on qubit - use right-aligned, constant length section for optimised timing behavior
with exp_echo.section(
uid="qubit_excitation",
length=echo_max + 2 * x90.length + x180.length,
alignment=SectionAlignment.RIGHT,
):
exp_echo.play(signal="drive", pulse=x90)
exp_echo.delay(signal="drive", time=echo_sweep)
exp_echo.play(signal="drive", pulse=x180)
exp_echo.delay(signal="drive", time=echo_sweep)
exp_echo.play(signal="drive", pulse=x90)
# readout pulse and data acquisition
readoutQubit(
exp_echo,
section_id="qubit_readout",
readout_id="measure",
acquire_id="acquire",
reserve_id="drive",
acquire_handle="q0_echo",
readout_pulse=readout_pulse,
readout_weights=readout_weighting_function,
relax_time=qubit_parameters["relax"],
)
3.6.3 Run and Evaluate Experiment for Both Qubits¶
- run for qubit 0¶
# set signal map for qubit 0
exp_echo.set_signal_map(q0_map)
# run experiment on qubit 0
my_results = my_session.run(exp_echo)
# Plot simulated output signals
plot_simulation(my_session.compiled_experiment, 360e-6, 5e-6)
- run for qubit 1¶
# set signal map for qubit 1
exp_echo.set_signal_map(q1_map)
# run experiment on qubit 1
my_results = my_session.run(exp_echo)
- plot and evaluate measurements results¶
# get measurement data returned by the instruments
echo_res = my_results.get_data("q0_echo")
# define time axis from qubit parameters
echo_delay = my_results.get_axis("q0_echo")[0]
if emulate:
# create dummy data if running in emulation mode
echo_res = exponential_decay(
echo_delay, 2 / qubit_parameters["T2_echo"], 0.1, -1
) + 0.1 * np.random.rand(len(echo_delay))
# plot measurement results - multiply delay by factor of two to get full sequence length
fig = plt.figure()
plt.plot(2 * echo_delay, echo_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(echo_delay[0], 2 * echo_delay[-1], 5 * len(echo_delay))
## fit measurement data to decaying sinusoidal oscillatio
popt, pcov = exponential_decay.fit(2 * echo_delay, echo_res, 1e6, 0, -1, plot=False)
print(f"Fitted parameters: {popt}")
# plot fit results together with experimental data
plt.plot(delay_plot, exponential_decay(delay_plot, *popt), "-r");
# update qubit parameters - here qubit dephasing time
qubit_parameters["T2_echo"] = 1 / popt[0]
qubit_parameters["T2_echo"]