Hahn echo T2#

Using pulsed mode, we perform a Hahn echo experiment: we repeat the Ramsey experiment from the previous chapter with an additional \(\pi\) pulse in the middle of the interferometry sequence. We send two \(\pi/2\) pulses to the qubit, separated by variable time \(\delta\). At half time between the two \(\pi/2\) pulses, we send a \(\pi\) pulse. The frequency of the pulses is the qubit frequency and their envelope has a \(\sin^2\) shape. The readout pulse is at the frequency of the readout resonator and has a square envelope.

Ramsey echo pulse sequence

The full source code for performing the Hahn echo experiment is available at presto-measure/ramsey_echo.py. Here we first run the experiment and then look into the most interesting parts of the code.

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

from ramsey_echo import RamseyEcho
import numpy as np

experiment = RamseyEcho(
    readout_freq=6.2e9,
    control_freq=4.2e9,
    readout_amp=0.1,
    control_amp_90=0.25,
    control_amp_180=0.5,
    readout_duration=2.1e-6,
    control_duration=100e-9,
    sample_duration=2.1e-6,
    delay_arr=np.linspace(0, 50e-6, 101),
    readout_port=1,
    control_port=4,
    sample_port=1,
    wait_delay=100e-6,
    readout_sample_delay=0e-9,
    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 = RamseyEcho.load("data/ramsey_echo_20220405_113517.h5")

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

experiment.analyze()
../_images/ramsey_echo_light.svg../_images/ramsey_echo_dark.svg

In the Hahn echo experiment, for delay \(\delta = 0\) we apply the equivalent of a \(2\pi\) pulse, so the qubit ends up in the ground state. For a delay much longer than the decoherence time, the qubit ends up in a mixed state at the center of the Bloch sphere. For any delay \(\delta\), we measure an exponential decay. The \(\pi\) pulse in the middle of the sequence “echoes out” a part of the pure dephasing noise so, usually, the Hahn echo time \(T_2\) is longer than the Ramsey decay time \(T_2^*\).

Code explanation#

Here we discuss the main parts of the code of the RamseyEcho class: the definition of the qubit-control pulses and the scheduling of the experimental sequence. The full source is available at presto-measure/ramsey_echo.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 choose to encode the \(\pi\) and \(\pi/2\) pulses as two separate templates with different amplitudes:

control_ns = int(round(self.control_duration * pls.get_fs("dac")))
control_envelope = sin2(control_ns) * (1.0 + 1j)

control_pulse_90 = pls.setup_template(
    self.control_port, group=0,
    template=self.control_amp_90 * control_envelope,
)
control_pulse_180 = pls.setup_template(
    self.control_port, group=0,
    template=self.control_amp_180 * control_envelope,
)

Because the amplitude is encoded directly into the templates, we program a single entry 1.0 in the scale look-up table (LUT) for the qubit control:

pls.setup_scale_lut(self.control_port, group=0, scales=1.0)

The pulse sequence is then very similar to that of the previous chapter Ramsey \(T_2^*\), but with the extra \(\pi\) pulse in between the two \(\pi/2\) pulses:

for delay in self.delay_arr:
    # first π/2 pulse
    pls.output_pulse(T, control_pulse_90)
    T += self.control_duration

    T += delay / 2  # wait first half of delay

    # π pulse (echo)
    pls.output_pulse(T, control_pulse_180)
    T += self.control_duration

    T += delay / 2  # wait second half of delay

    # second π/2 pulse
    pls.output_pulse(T, control_pulse_90)
    T += self.control_duration

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

    T += self.wait_delay  # wait for decay

Alternatively, we could have used a single template for both the \(\pi\) and the \(\pi/2\) pulses, and encoded the different amplitudes as two entries in the scale LUT:

control_ns = int(round(self.control_duration * pls.get_fs("dac")))
control_envelope = sin2(control_ns) * (1.0 + 1j)
control_pulse = pls.setup_template(
    output_port=self.control_port, group=0,
    template=control_envelope,
)

pls.setup_scale_lut(self.control_port, group=0, scales=[self.control_amp_90, self.control_amp_180])

Then in the pulse sequence we would output the same pulse three times, but we would move the pointer of the scale LUT to the desired amplitude using select_scale() before each pulse. Remember that Python arrays are indexed starting from 0.

for delay in self.delay_arr:
    # first π/2 pulse
    pls.select_scale(T, index=0, output_ports=self.control_port)
    pls.output_pulse(T, control_pulse)
    T += self.control_duration

    T += delay / 2  # wait first half of delay

    # π pulse (echo)
    pls.select_scale(T, index=1, output_ports=self.control_port)
    pls.output_pulse(T, control_pulse)
    T += self.control_duration

    T += delay / 2  # wait second half of delay

    # second π/2 pulse
    pls.select_scale(T, index=0, output_ports=self.control_port)
    pls.output_pulse(T, control_pulse)
    T += self.control_duration

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

    T += self.wait_delay  # wait for decay

When performing simple experiments such as this Hahn echo, both approaches are equivalent. For more resource-intensive experiments, it is good to keep in mind that the same experiment can often be implemented in different ways on Presto, and depending on the specific case some approaches can be more efficient or flexible than others.