See also
A Jupyter notebook version of this tutorial can be downloaded here.

Echo#
The Hahn echo experiment is a modified Ramsey sequence that uses a refocusing \(\pi\)-pulse at the midpoint of the free evolution time (t = \(\tau\)/2) to invert the phase evolution, which effectively cancels out phase accumulations due to frequency offsets that are constant within the duration of the experiment (low-f noise). The fitted decay time \(T_{2,e}\) can be compared to \(T_2^*\) to estimate low-frequency noise.
[1]:
from qblox_applications.qblox_scheduler.analysis import EchoAnalysis
from qblox_applications.qblox_scheduler.superconducting.transmon import (
BasicTransmonElement, # noqa: F401
)
from xarray import open_dataset
from qblox_scheduler import (
X90,
DType,
HardwareAgent,
IdlePulse,
Measure,
Reset,
Schedule,
SetClockFrequency,
X,
arange,
)
Setup#
The hardware agent manages the connection to the instrument and ensures that pulses and acquisitions happen over the appropriate input and output channels of the Cluster. The cell below creates an instance of the HardwareAgent based on the hardware- and device-under-test configuration files in the ./dependencies/configs folder, allowing us to start doing measurements. We also define some convenient aliases to use throughout our measurements. For a more thorough discussion of the
hardware- and device-under-test configuration files, check out this tutorial.
[2]:
# Set up hardware agent, this automatically connects to the instrument
hw_agent = HardwareAgent(
hardware_configuration="./dependencies/configs/hw_config.json",
quantum_device_configuration="./dependencies/configs/dut_config.json",
)
# convenience aliases
q0 = hw_agent.quantum_device.get_element("q0") # Qubits 0 and 2 are measured using QRM-RF + QCM-RF
q2 = hw_agent.quantum_device.get_element("q2")
q3 = hw_agent.quantum_device.get_element("q3") # Qubit 3 is measured using QRC
cluster = hw_agent.get_clusters()["cluster"]
hw_options = hw_agent.hardware_configuration.hardware_options
qubit = q0
/.venv/lib/python3.14/site-packages/qblox_scheduler/qblox/hardware_agent.py:603: UserWarning: cluster: Trying to instantiate cluster with ip 'None'.Creating a dummy cluster.
warnings.warn(
Experiment settings#
Note: in order for the second \({\pi/2}\) pulse to have a phase difference \(\Delta \phi = \Delta f \tau\), relative to the first \(X_{\pi/2}\) pulse we offset the clock frequency by \(\Delta f\) for a time \(\tau\). Here, \(\Delta f\) is the frequency_detuning and \(\tau\) is tau.
[3]:
# Detuning
frequency_detuning = 1e6 # Hz
# Tau settings
tau_start = 1e-6 # s
tau_stop = 100e-6 # s
tau_step = 2e-6 # s
repetitions = 1000
Experiment schedule#
[4]:
echo_sched = Schedule(name="echo_experiment")
echo_sched.add(
SetClockFrequency(
clock=qubit.name + ".01", frequency=qubit.clock_freqs.f01 + frequency_detuning
)
)
# Update parameters
echo_sched.add(IdlePulse(4e-9))
with (
echo_sched.loop(arange(0, repetitions, 1, DType.NUMBER)),
echo_sched.loop(arange(start=tau_start, stop=tau_stop, step=tau_step, dtype=DType.TIME)) as tau,
):
echo_sched.add(Reset(qubit.name))
# Play first X/2 pulse
echo_sched.add(X90(qubit=qubit.name))
# Add reflecting pi pulse at time tau / 2
echo_sched.add(X(qubit=qubit.name), rel_time=tau / 2)
# Play second X/2 pulse tau / 2 seconds after pi pulse
echo_sched.add(X90(qubit=qubit.name), rel_time=tau / 2)
echo_sched.add(Measure(qubit.name, coords={"tau": tau}, acq_label="S_21"))
# Execute the experiment
echo_data = hw_agent.run(echo_sched)
if cluster.is_dummy:
example_data = open_dataset("./dependencies/datasets/echo.hdf5", engine="h5netcdf")
echo_data.update({"S_21": example_data.S_21})
Analyze the experiment#
[5]:
echo_analysis = EchoAnalysis(echo_data).run()
echo_analysis.display_figs_mpl()
findfont: Failed to find font weight 300 for STIXGeneral, now using 400.
findfont: Failed to find font weight 300 for STIXGeneral, now using 400.
findfont: Failed to find font weight 300 for STIXNonUnicode, now using 400.
findfont: Failed to find font weight 300 for STIXNonUnicode, now using 400.
findfont: Failed to find font weight 300 for STIXSizeOneSym, now using 400.
findfont: Failed to find font weight 300 for STIXSizeTwoSym, now using 400.
findfont: Failed to find font weight 300 for STIXSizeThreeSym, now using 400.
findfont: Failed to find font weight 300 for STIXSizeFourSym, now using 400.
findfont: Failed to find font weight 300 for STIXSizeFiveSym, now using 400.
findfont: Failed to find font weight 300 for cmmi10, now using 400.
findfont: Failed to find font weight 300 for cmsy10, now using 400.
findfont: Failed to find font weight 300 for cmr10, now using 400.
findfont: Failed to find font weight 300 for cmtt10, now using 400.
findfont: Failed to find font weight 300 for cmti10, now using 400.
findfont: Failed to find font weight 300 for cmb10, now using 400.
findfont: Failed to find font weight 300 for cmss10, now using 400.
findfont: Failed to find font weight 300 for cmex10, now using 400.
findfont: Failed to find font weight 300 for DejaVu Sans, now using 400.
findfont: Failed to find font weight 300 for DejaVu Sans, now using 400.
findfont: Failed to find font weight 300 for DejaVu Sans Mono, now using 400.
findfont: Failed to find font weight 300 for DejaVu Sans Display, now using 400.
Update the device configuration file#
After measurement, we may store the measured device properties inside a new file to use in future experiments. The time-unique identifier ensures that it is easy to find back previously found measurement results.
[6]:
hw_agent.quantum_device.to_json_file("./dependencies/configs", add_timestamp=True)
[6]:
'./dependencies/configs/two_flux_tunable_transmons_2026-10-15_13-58-37_UTC.json'