Resonator spectroscopy¶
Using lockin
mode, we do a simple single-frequency sweep.
The example script for performing the resonator spectroscopy experiment is in the file presto-measure/sweep.py. Here, we first run the experiment and fit the resonator response, then we have a deeper look at the code used to perform the measurement.
You can create a new experiment and run it on your Presto. Be sure to change the parameters of
Sweep
to match your experiment and change presto_address
to match the IP address of your
Presto.
from sweep import Sweep
experiment = Sweep(
freq_center=6e9,
freq_span=5e6,
df=100e3,
num_averages=100,
amp=0.1,
output_port=1,
input_port=1,
)
presto_address = "192.168.88.65" # your Presto IP address
save_filename = experiment.run(presto_address)
Or you can also load older data:
experiment = Sweep.load("data/sweep_20220331_163206.h5")
In either case, we analyze the data to get a nice plot:
experiment.analyze()
When the experiment is done, click and drag on the plot panel to select a region for the fit. This will fit the resonance with the circle-fit method and print the relevant fitted parameters. Note that the Python package resonator_tools should be installed for this to work.
----------------
fr = 6028146404.689569
Qi = 221104.1461204515
Qc = 12105.122298585347
Ql = 11476.785411046161
kappa = 497983.1063246707
f_min = 6028320000.0
----------------
Code explanation¶
Here we look under the hood of the Sweep
class and discuss the main parts of the code. See the
full source at presto-measure/sweep.py.
We start by creating the lck
object, an instance of the Lockin
class: this will connect
to the Presto unit at the IP address presto_address
and set up the reference clock
(ext_ref_clk
). We also tell the API we want to use digital up- and down-conversion, by setting
both AdcMode
and DacMode
to the Mixed
variant. If we didn’t want to use the
digital mixers, we’d use the default Direct
variant instead. See the documentation for the
Lockin
class and Advanced tile configuration for more details.
with lockin.Lockin(
address=presto_address,
ext_ref_clk=ext_ref_clk,
adc_mode=AdcMode.Mixed,
dac_mode=DacMode.Mixed,
) as lck:
We tune the integration bandwidth df
: this ensures that the chosen df
is commensurate with the
sampling frequency and if not, it will slightly change it. You can read more in the documentation
for tune()
.
_, self.df = lck.tune(0.0, self.df)
lck.set_df(self.df)
We set up the digital IQ mixer for up- and down-conversion using Hardware.configure_mixer()
.
We start with the first frequency here, and we’ll update the frequency in a loop later on to
perform the sweep.
freq = self.freq_arr[0]
lck.hardware.configure_mixer(freq, in_ports=self.input_port, out_ports=self.output_port)
Once the digital up- and down-conversion is set up, we proceed with the intermediate frequency (IF)
configuration. We create an OutputGroup
and an InputGroup
, each with a single
frequency. These groups map a number of the available output and input tones to output and input
ports. We opt for using zero IF, so the spectroscopy tone will coincide with the local-oscillator
frequency set in the mixer.
In an external, analog frequency conversion scheme you typically don’t want to use zero IF to avoid \(1/f\) noise and isolate your signal from mixing artifacts like LO leakage and mixer imbalance. Presto, on the other hand, uses digital frequency conversion so these concerns are not applicable and using zero IF is a good way of simplifying the signal chain.
og = lck.add_output_group(self.output_port, nr_freq=1)
og.set_frequencies(0.0)
og.set_amplitudes(self.amp)
og.set_phases(0.0, 0.0)
ig = lck.add_input_group(self.input_port, nr_freq=1)
ig.set_frequencies(0.0)
We complete the initial configuration by applying the settings to the hardware with
apply_settings()
. At this point, Presto will start outputting the spectroscopy tone.
At very low output power, set_dither()
can help to reduce nonlinearity by adding
pseudorandom noise to the output.
lck.set_dither(self.dither, self.output_port)
lck.apply_settings()
To perform the actual frequency sweep, we loop through the frequency array and update the mixer
settings. After making sure the settings are applied, we obtain the measured data with
get_pixels()
.
for ii, freq in enumerate(self.freq_arr):
lck.hardware.configure_mixer(freq, in_ports=self.input_port, out_ports=self.output_port)
lck.apply_settings()
_d = lck.get_pixels(self.num_skip + self.num_averages, quiet=True)
data_i = _d[self.input_port][1][:, 0] # in-phase data
data_q = _d[self.input_port][2][:, 0] # quadrature data
data = data_i.real + 1j * data_q.real # using zero IF
self.resp_arr[ii] = np.mean(data[-self.num_averages :])
Finally, after the sweep is complete, we turn off the spectroscopy tone by setting the amplitude to zero.
og.set_amplitudes(0.0)
lck.apply_settings()