Single shot readout¶
Using pulsed
mode and template matching we can demodulate and integrate the resonator response based on the qubit state without averaging. This means we get a single point in the IQ-plane for each measurement perfomed. We interleave preparing qubit in the ground state (no \(\pi\) pulse) and preparing qubit in the excited state (\(\sin^2\) envelope \(\pi\) pulse) followed by a readout pulse (square pulse) and we perfom this measurement nr_averages
times. We get two blobs each representing qubit in either ground or excited state.
The full source code for this experiment is available at presto-measure/single_shot_readout.py. Here, we first run the single shot readout experiment and analyze the data, 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
SingleShotReadout
to match your experiment and change presto_address
to match the IP address of your
Presto:
from single_shot_readout import SingleShotReadout
import numpy as np
experiment = SingleShotReadout(
readout_freq=7.81438e9,
control_freq=5.5229793e9,
readout_amp=0.08,
control_amp=0.176,
readout_duration=1000e-9,
control_duration=50e-9,
readout_sample_delay=0e-9,
sample_duration=2500e-9,
readout_port=1,
control_port=4,
sample_port=1,
wait_delay=400e-6,
template_match_start=268e-9,
template_match_duration=800e-9,
template_match_phase=0.0,
num_averages=5000,
)
presto_address = "192.168.88.65" # your Presto IP address
save_filename = experiment.run(presto_address)
Or you can also load older data:
experiment = SingleShotReadout.load("../data/single_shot_readout_20230622_145840.h5")
In either case, we analyze the data to get a nice plot:
experiment.analyze(rotate_optimally=False)
Each point represents one demodulated and integrated measurement (blue/orange markers for the qubit in the ground/excited state).
Measured points can be rotated in post-processing so that all the information is contained in only I-quadrature. The angle of rotation can be used to set the template_match_phase
so that the templates are rotated in the IQ-plane such that all the information is contained in one quardature.
experiment.analyze(rotate_optimally=True)
Code explanation¶
Here we discuss the main parts of the code in presto-measure/single_shot_readout.py.
Note
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 two amplitudes in the scale_lut. Zero amplitude to keep the qubit in the ground state and self.control_amp
to apply a \(\pi\) pulse.
pls.setup_scale_lut(self.control_port, group=0, scales=[0, self.control_amp])
We then set the matching templates similarly to setting any other templates. They will be multiplied element vise with the demodulated sampled data and the result will be summed. In this example, we choose the simplest template, the square of template_match_duration
duration. We set the matching templates in pairs. So we use template1
to integrate the I-quadrature and template2
to integrate the Q-quadrature.
shape = np.ones(int(round(self.template_match_duration * pls.get_fs("dac"))))
match_events = pls.setup_template_matching_pair(
input_port=self.sample_port,
template1=shape * np.exp(self.template_match_phase),
template2=1j * shape * np.exp(self.template_match_phase),
)
The template_match_phase
determines the angle of the IQ coordinate system allowing us to rotate the readout data.
The core of the measurement is the definition of the experimental sequence:
for i in range(2):
pls.select_scale(T, i, self.control_port, group=0)
pls.output_pulse(T, [control_pulse])
T += self.control_duration
pls.output_pulse(T, [readout_pulse])
pls.store(T + self.readout_sample_delay)
pls.match(T + self.template_match_start, [match_events])
T += self.readout_duration + self.wait_delay
We use a for
loop to either select zero amplitude or the amplitude of the \(\pi\) pulse and output the control_pulse
. As usual, the resonator-readout pulse and the data acquisition are scheduled right after the control pulse. The matching has to be scheduled within a store
window, and finally we wait for the qubit to decay back to the ground state.
To finally execute the measurement, we call run()
:
pls.run(period=T, repeat_count=1, num_averages=self.num_averages)
self.t_arr, self.store_arr = pls.get_store_data()
self.match_arr = pls.get_template_matching_data([match_events])
Appart from the usual averaged store_arr
result, we obtain the match_arr
by calling get_template_matching_data()
. len(match_arr)
is the same as the number of matching templates. This is two in our example, as we are setup one template_matching_pair
. match_arr[0]
contains the outcomes of matching with template1
(I-quadrature) in chronological order. Similarly, match_arr[1]
contains the outcomes of matching with template2
(Q-quadrature). We performed 5000 averages, meaning 10000 measurements in total (5000 ground and 5000 excited qubit state measurements). These measurements are interleaved and so are the template matching results.
complex_match_data = self.match_arr[0] + 1j * self.match_arr[1]
ground_data = complex_match_data[::2]
excited_data = complex_match_data[1::2]
So we simply pick out every other element of the array to separate the ground_data
from the excited_data
.