Rabi time

Using pulsed mode, we measure Rabi oscillations by driving the qubit with a square pulse of variable duration. We also sweep the amplitude of the qubit pulse to get a 2D sweep. We fit the amplitude-dependent Rabi rate.

Rabi time pulse sequence

The full source code for this experiment is available at presto-measure/rabi_time.py. Here, we first run the Rabi experiment and analyze the data in order to fit the amplitude-dependent Rabi rate, and then we have a more detailed look at the most important parts of the code.

You can create a new experiment and run it on your Presto. Be sure to change the parameters of RabiTime to match your experiment and change presto_address to match the IP address of your Presto:

from rabi_time import RabiTime
import numpy as np

experiment = RabiTime(
    control_amp_arr=np.linspace(0.001, 0.01, 21),
    control_duration_arr=np.linspace(100e-9, 10e-6, 100),

presto_address = ""  # your Presto IP address
save_filename = experiment.run(presto_address)

Or you can also load older data:

experiment = RabiTime.load("data/rabi_time_20230328_181549.h5")

In either case, we analyze the data to get a nice plot:


The frequency of Rabi oscillations (left panel) is fitted for each drive amplitude individually. The linear dependence of the Rabi rate and drive amplitude is shown in the right panel.

Code explanation

Here we discuss the main parts of the code in presto-measure/rabi_time.py.


If this is your first measurement in pulsed mode, you might want to first have a look at the Rabi amplitude chapter in this tutorial. There we describe the code more pedagogically and in more detail.

We program all the control amplitudes we want to sweep in the scale look-up table (LUT), just like we did in Rabi amplitude.

pls.setup_scale_lut(self.readout_port, group=0, scales=self.readout_amp)
pls.setup_scale_lut(self.control_port, group=0, scales=self.control_amp_arr)

We want to change the duration of the qubit-control pulse for each iteration of the experiment. The easiest way to achieve that is to create a LongDrive using setup_long_drive():

control_pulse = pls.setup_long_drive(
    self.control_port, group=0,
    amplitude=1.0 + 1j,

We initialize the pulse duration to the first value in control_duration_arr, we will then update the duration when we program the experimental sequence. A LongDrive supports smooth rise and fall, but in this case we’ll just use a square pulse shape.

The core of the measurement is the definition of the experimental sequence:

T = 0.0  # s, start at time zero ...
for control_duration in self.control_duration_arr:
    control_pulse.set_total_duration(control_duration)  # Set control pulse length
    pls.output_pulse(T, control_pulse)
    T += control_duration

    pls.output_pulse(T, readout_pulse)  # Readout
    pls.store(T + self.readout_sample_delay)
    T += self.readout_duration

    T += self.wait_delay  # Wait for decay

pls.next_scale(T, self.control_port, group=0)
T += self.wait_delay

We use a for loop to repeat the measurement for each duration of the qubit-control pulse. We update the length of the qubit-control pulse with set_total_duration(), and then we schedule its output.

As usual, the resonator-readout pulse and the data acquisition are scheduled right after the control pulse. After that, we wait for the qubit to decay back to the ground state.

When the for loop is exhausted, we call next_scale() to change the amplitude of the qubit-control pulse to the next entry in the scale LUT.

To finally execute the measurement, we call run():

nr_amps = len(self.control_amp_arr)
pls.run(period=T, repeat_count=nr_amps, num_averages=self.num_averages)

A single sequence already contains len(control_duration_arr) measurements due to the for loop, each with a different duration for the qubit-control pulse. This long sequence is then repeated nr_amps times due to the repeat_count parameter, each with a different amplitude of the qubit-control pulse. Finally, the whole 2D sweep is repeated num_averages times to perform interleaved averaging.