Two-tone spectroscopy of the qubit

Two-tone spectroscopy using lockin mode. 2D sweep of amplitude and frequency of the qubit drive called pump or control drive, with fixed frequency and amplitude of the readout resonator drive, also called the probe drive.

The source code for the class TwoTonePower that performs the two-tone spectroscopy experiment is available at presto-measure/two_tone_power.py. Here, we first run the experiment and extract a rough estimate of the qubit frequency, and we then have a deeper look at what the code actually does.

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

from two_tone_power import TwoTonePower
import numpy as np

experiment = TwoTonePower(
    readout_freq=6.2e9,
    control_freq_center=4.2e9,
    control_freq_span=200e6,
    df=200e3,
    readout_amp=0.2,
    control_amp_arr=np.linspace(0.1, 1, 11),
    readout_port=1,
    control_port=4,
    input_port=1,
    num_averages=100,
)

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

Or you can also load older data:

experiment = TwoTonePower.load("data/two_tone_power_20220422_063812.h5")

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

experiment.analyze()
../_images/two_tone_power_light.png ../_images/two_tone_power_dark.png

Code explanation

Let’s look at the main parts of the code in presto-measure/two_tone_power.py. Compared to what we saw in Power sweep of resonator spectroscopy, we output one more tone at one more port.

We still tune() the integration bandwidth before outputting the tones:

_, self.df = lck.tune(0.0, self.df)
lck.set_df(self.df)

We need to configure three digital mixers: one for the down-conversion at the input, and two for the up-conversion at the output. We end up with two calls to Hardware.configure_mixer(). For the pump tone on the qubit, we start with the first frequency in the array.

lck.hardware.configure_mixer(
    freq=self.readout_freq,
    in_ports=self.input_port,
    out_ports=self.readout_port,
)
lck.hardware.configure_mixer(
    freq=self.control_freq_arr[0],
    out_ports=self.control_port,
)

We create separate OutputGroups for the drives to the resonator (readout, ogr) and qubit (control, ogc). We also create an InputGroup ig. Like in the previous chapters of the tutorial, we opt for using zero IF so the spectroscopy tones will coincide with the local-oscillator frequency set in the respective mixers. We output and measure one frequency per input/output group.

ogr = lck.add_output_group(self.readout_port, nr_freq=1)
ogr.set_frequencies(0.0)
ogr.set_amplitudes(self.readout_amp)

ogc = lck.add_output_group(self.control_port, nr_freq=1)
ogc.set_frequencies(0.0)
ogc.set_amplitudes(self.control_amp_arr[0])

ig = lck.add_input_group(self.input_port, nr_freq=1)
ig.set_frequencies(0.0)

We apply the settings and, at this point, Presto will start outputting both spectroscopy tones to the resonator and to the qubit.

lck.apply_settings()

Just like in the previous chapter, the core of the measurement consists of two nested for loops. The two loops update the settings for the qubit drive, while the resonator drive is kept constant. The outer loop sweeps the qubit amplitude. In the inner loop, we update the qubit frequency, wait for the settings to apply and the resonator response to stabilize, and finally acquire measurement data with get_pixels(). Notice that we do not change the resonator drive tone within the loops since we already started outputting it before the start of the first for loop.

for jj, control_amp in enumerate(self.control_amp_arr):
    ogc.set_amplitudes(control_amp)
    lck.apply_settings()

    for ii, control_freq in enumerate(self.control_freq_arr):
        lck.hardware.configure_mixer(control_freq, out_ports=self.control_port)
        lck.apply_settings()

        _d = lck.get_pixels(self.num_skip + self.num_averages, quiet=True)
        data_i = _d[self.input_port][1][:, 0]
        data_q = _d[self.input_port][2][:, 0]
        data = data_i.real + 1j * data_q.real  # using zero IF

        self.resp_arr[jj, ii] = np.mean(data[-self.num_averages :])

Finally, after the experiment is complete, we set the amplitude of both outputs to zero.

ogr.set_amplitudes(0.0)
ogc.set_amplitudes(0.0)
lck.apply_settings()