See also

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

image0

Fixed Frequency Transmon#

This notebook shows a typical tuneup sequence for fixed frequency transmon qubits. The notebook aims to be as compact as possible, for more details, please refer to the respective experiments.

[1]:
from __future__ import annotations

from functools import partial
from pathlib import Path
from typing import Literal

import matplotlib.pyplot as plt
import numpy as np
import rich  # noqa:F401
from qcodes.parameters import ManualParameter
from simulated_data import get_simulated_res_spec_data, get_simulated_tof_data
from sklearn.metrics import ConfusionMatrixDisplay

import quantify_core.data.handling as dh
from quantify_core.analysis import RabiAnalysis
from quantify_core.analysis.base_analysis import Basic2DAnalysis
from quantify_core.analysis.readout_calibration_analysis import ReadoutCalibrationAnalysis
from quantify_core.analysis.single_qubit_timedomain import RamseyAnalysis, T1Analysis
from quantify_core.analysis.spectroscopy_analysis import (
    QubitSpectroscopyAnalysis,
    ResonatorSpectroscopyAnalysis,
)
from quantify_core.analysis.time_of_flight_analysis import TimeOfFlightAnalysis
from quantify_core.data.handling import load_dataset
from quantify_scheduler import QuantumDevice, Schedule, ScheduleGettable
from quantify_scheduler.backends.qblox import constants
from quantify_scheduler.device_under_test.transmon_element import BasicTransmonElement
from quantify_scheduler.enums import BinMode
from quantify_scheduler.math import closest_number_ceil
from quantify_scheduler.operations import X90, IdlePulse, Measure, Reset, Rxy, X
from quantify_scheduler.operations.acquisition_library import SSBIntegrationComplex
from quantify_scheduler.operations.pulse_library import (
    SetClockFrequency,
    SquarePulse,
)
from quantify_scheduler.resources import ClockResource

from utils import display_dict, initialize_hardware, run_schedule, show_connectivity  # noqa:F401
[2]:
hw_config_path = "configs/tuning_transmon_coupled_pair_hardware_config.json"
device_path = "devices/transmon_device_2q.json"
[3]:
# Enter your own dataset directory here!
dh.set_datadir(dh.default_datadir())
Data will be saved in:
/root/quantify-data
[4]:
quantum_device = QuantumDevice("transmon_device")
quantum_device.hardware_config.load_from_json_file(hw_config_path)
qubit = BasicTransmonElement("q0")
qubit.measure.acq_channel(0)
quantum_device.add_element(qubit)
cluster_ip = None
meas_ctrl, inst_coord, cluster = initialize_hardware(quantum_device, ip=cluster_ip)

Time of flight measurement#

Here show how to measure time of flight for your system. This is useful to calibrate the acquisition delay for subsequent experiments.

Schedule definition#

[5]:
def tof_trace_schedule(
    qubit_name: str,
    repetitions: int = 1,
) -> Schedule:
    schedule = Schedule("Trace measurement schedule", repetitions=repetitions)
    schedule.add(Measure(qubit_name, acq_protocol="Trace"))
    return schedule

Measuring time of flight with trace acquisition#

[6]:
def set_readout_attenuation_hardware_config(attenuation_dB: int):
    hwcfg = quantum_device.hardware_config()
    output_att = hwcfg["hardware_options"]["output_att"]
    output_att[f"{qubit.ports.readout()}-{qubit.name}.ro"] = attenuation_dB
    quantum_device.hardware_config(hwcfg)


set_readout_attenuation_hardware_config(0)
qubit.measure.pulse_duration(300e-9)
qubit.measure.integration_time(1e-6)
qubit.measure.pulse_amp(0.1)
qubit.measure.acq_delay(4e-9)
qubit.clock_freqs.readout(7.2e9)
[7]:
tof_t = ManualParameter(name="tof_t", unit="ns", label="Trace acquisition sample")
tof_t.batched = True
tof_t.batch_size = round(qubit.measure.integration_time() * constants.SAMPLING_RATE)

tof_sched_kwargs = dict(
    qubit_name=qubit.name,
)

# set gettable
gettable = ScheduleGettable(
    quantum_device,
    schedule_function=tof_trace_schedule,
    schedule_kwargs=tof_sched_kwargs,
    real_imag=False,
    batched=True,
)

# set measurement control
meas_ctrl.gettables(gettable)
[8]:
tof_t_setpoints = np.arange(tof_t.batch_size)

meas_ctrl.settables(tof_t)
meas_ctrl.setpoints(tof_t_setpoints)

if cluster_ip is None:

    def dummy_gettable():
        gettable.initialize()
        return get_simulated_tof_data()

    gettable.get = dummy_gettable

tof_ds = dh.to_gridded_dataset(meas_ctrl.run("Time of flight measurement " + qubit.name))
Starting batched measurement...
Iterative settable(s) [outer loop(s)]:
         --- (None) ---
Batched settable(s):
         tof_t
Batch size limit: 1000

Completed: 100%
 [ elapsed time: 00:00 | time left: 00:00 ]  last batch size: 1000

Analysis#

[9]:
# use fake data in case the cluster is a dummy
tof_analysis = TimeOfFlightAnalysis(tuid=dh.get_latest_tuid())
tof_analysis.run(playback_delay=149e-9).display_figs_mpl()
../../../_images/applications_quantify_transmon_fixed_freq_transmon_14_0.png
[10]:
fit_results = tof_analysis.quantities_of_interest
nco_prop_delay = fit_results["nco_prop_delay"]
measured_tof = fit_results["tof"]

qubit.measure.acq_delay(
    closest_number_ceil(
        measured_tof * constants.SAMPLING_RATE, constants.MIN_TIME_BETWEEN_OPERATIONS
    )
    / constants.SAMPLING_RATE
)
[11]:
quantum_device.to_json_file("devices/")
[11]:
'devices/transmon_device_2025-03-12_18-03-50_UTC.json'

Resonator Spectroscopy#

Here we go through resonator discovery and punchout spectroscopy for identifying the resonator and measuring it’s resonant frequency.

[12]:
def set_readout_attenuation_hardware_config(attenuation_dB: int):
    hwcfg = quantum_device.hardware_config()
    output_att = hwcfg["hardware_options"]["output_att"]
    output_att[f"{qubit.ports.readout()}-{qubit.name}.ro"] = attenuation_dB
    quantum_device.hardware_config(hwcfg)


set_readout_attenuation_hardware_config(32)
qubit.measure.acq_delay(452e-9)

Resonator Spectroscopy#

[13]:
# This schedule can also be imported rom quantify_scheduler.schedules


def heterodyne_spec_sched_nco(
    pulse_amp: float,
    pulse_duration: float,
    frequencies: np.ndarray,
    acquisition_delay: float,
    integration_time: float,
    port: str,
    clock: str,
    init_duration: float = 10e-6,
    repetitions: int = 1,
    port_out: str | None = None,
) -> Schedule:
    """
    Generate a batched schedule for performing fast heterodyne spectroscopy.

    Using the :class:`~quantify_scheduler.operations.pulse_library.SetClockFrequency`
    operation for doing an NCO sweep.

    Parameters
    ----------
    pulse_amp
        Amplitude of the spectroscopy pulse in Volt.
    pulse_duration
        Duration of the spectroscopy pulse in seconds.
    frequencies
        Sample frequencies for the spectroscopy pulse in Hertz.
    acquisition_delay
        Start of the data acquisition with respect to the start of the spectroscopy
        pulse in seconds.
    integration_time
        Integration time of the data acquisition in seconds.
    port
        Location on the device where the acquisition is performed.
    clock
        Reference clock used to track the spectroscopy frequency.
    init_duration
        The relaxation time or dead time.
    repetitions
        The amount of times the Schedule will be repeated.
    port_out
        Output port on the device where the pulse should be applied. If `None`, then use
        the same as `port`.

    """
    sched = Schedule("Fast heterodyne spectroscopy (NCO sweep)", repetitions)
    sched.add_resource(ClockResource(name=clock, freq=frequencies.flat[0]))

    if port_out is None:
        port_out = port

    for acq_idx, freq in enumerate(frequencies):
        sched.add(IdlePulse(duration=init_duration), label=f"buffer {acq_idx}")

        sched.add(
            SetClockFrequency(clock=clock, clock_freq_new=freq),
            label=f"set_freq {acq_idx} ({clock} {freq:e} Hz)",
        )

        spec_pulse = sched.add(
            SquarePulse(
                duration=pulse_duration,
                amp=pulse_amp,
                port=port_out,
                clock=clock,
            ),
            label=f"spec_pulse {acq_idx})",
        )

        sched.add(
            SSBIntegrationComplex(
                duration=integration_time,
                port=port,
                clock=clock,
                acq_index=acq_idx,
                acq_channel=0,
                bin_mode=BinMode.AVERAGE,
            ),
            ref_op=spec_pulse,
            ref_pt="start",
            rel_time=acquisition_delay,
            label=f"acquisition {acq_idx})",
        )

    return sched
[14]:
freqs = ManualParameter(name="freq", unit="Hz", label="Frequency")
freqs.batched = True
freqs.batch_size = 100
center = 7.364e9
frequency_setpoints = np.linspace(center - 10e6, center + 10e6, 300)

spec_sched_kwargs = dict(
    pulse_amp=1 / 6,
    pulse_duration=2e-6,
    frequencies=freqs,
    acquisition_delay=196e-9,
    integration_time=2e-6,
    init_duration=10e-6,
    port=qubit.ports.readout(),
    clock=qubit.name + ".ro",
)
gettable = ScheduleGettable(
    quantum_device,
    schedule_function=heterodyne_spec_sched_nco,
    schedule_kwargs=spec_sched_kwargs,
    real_imag=False,
    batched=True,
)


def simulated_get():
    gettable.old_get()
    return get_simulated_res_spec_data(f=frequency_setpoints, fr=center)


if cluster_ip is None:
    gettable.old_get = gettable.get
    gettable.get = simulated_get

meas_ctrl.gettables(gettable)
[15]:
quantum_device.cfg_sched_repetitions(400)
meas_ctrl.settables(freqs)
meas_ctrl.setpoints(frequency_setpoints)
rs_ds = meas_ctrl.run("resonator spectroscopy")
rs_ds
Starting batched measurement...
Iterative settable(s) [outer loop(s)]:
         --- (None) ---
Batched settable(s):
         freq
Batch size limit: 100

/builds/qblox/packages/software/qblox_instruments_docs/.venv/lib/python3.9/site-packages/quantify_scheduler/backends/circuit_to_device.py:414: RuntimeWarning: Clock 'q0.ro' has conflicting frequency definitions: 7354000000.0 Hz in the schedule and 7200000000.0 Hz in the device config. The clock is set to '7354000000.0'. Ensure the schedule clock resource matches the device config clock frequency or set the clock frequency in the device config to np.NaN to omit this warning.
  warnings.warn(
Completed: 100%
 [ elapsed time: 00:00 | time left: 00:00 ]  last batch size: 100
[15]:
<xarray.Dataset> Size: 7kB
Dimensions:  (dim_0: 300)
Coordinates:
    x0       (dim_0) float64 2kB 7.354e+09 7.354e+09 ... 7.374e+09 7.374e+09
Dimensions without coordinates: dim_0
Data variables:
    y0       (dim_0) float64 2kB 13.06 13.2 13.38 13.28 ... 15.17 14.74 14.69
    y1       (dim_0) float64 2kB -134.4 -129.5 -124.9 ... 6.948 12.39 17.69
Attributes:
    tuid:                             20250312-180350-353-12cc7d
    name:                             resonator spectroscopy
    grid_2d:                          False
    grid_2d_uniformly_spaced:         False
    1d_2_settables_uniformly_spaced:  False
[16]:
rs_analysis = ResonatorSpectroscopyAnalysis(
    dataset=rs_ds, settings_overwrite={"mpl_transparent_background": False}
)
rs_analysis.run().display_figs_mpl()
../../../_images/applications_quantify_transmon_fixed_freq_transmon_24_0.png
../../../_images/applications_quantify_transmon_fixed_freq_transmon_24_1.png
../../../_images/applications_quantify_transmon_fixed_freq_transmon_24_2.png
[17]:
qubit.clock_freqs.readout(rs_analysis.quantities_of_interest["fr"].nominal_value)

Resonator punchout#

[18]:
def resonator_punchout_schedule(
    qubit,  # noqa: ANN001
    freqs: np.array,
    ro_pulse_amps: np.array,
    repetitions: int = 1,
) -> Schedule:
    """Schedule to sweep the resonator frequency."""
    sched = Schedule("schedule", repetitions=repetitions)
    index = 0
    freqs, ro_pulse_amps = np.unique(freqs), np.unique(ro_pulse_amps)
    for freq in freqs:
        for amp in ro_pulse_amps:
            sched.add(Measure(qubit.name, acq_index=index, freq=freq, pulse_amp=amp))
            sched.add(IdlePulse(8e-9))
            index += 1
    return sched


freqs = ManualParameter(name="freq", unit="Hz", label="Frequency")
freqs.batched = True
ro_pulse_amps = ManualParameter(name="ro_pulse_amp", unit="", label="Readout pulse amplitude")
ro_pulse_amps.batched = False

spec_sched_kwargs = dict(
    qubit=qubit,
    freqs=freqs,
    ro_pulse_amps=ro_pulse_amps,
)

gettable = ScheduleGettable(
    quantum_device,
    schedule_function=resonator_punchout_schedule,
    schedule_kwargs=spec_sched_kwargs,
    real_imag=False,
    batched=True,
)

meas_ctrl.gettables(gettable)
[19]:
quantum_device.cfg_sched_repetitions(80)
center = qubit.clock_freqs.readout()
frequency_setpoints = np.linspace(center - 7e6, center + 3e6, 400)
amplitude_setpoints = np.linspace(0.01, 0.5, 10)

meas_ctrl.settables([freqs, ro_pulse_amps])
meas_ctrl.setpoints_grid((frequency_setpoints, amplitude_setpoints))

punchout_ds = meas_ctrl.run("resonator punchout")
punchout_ds
Starting batched measurement...
Iterative settable(s) [outer loop(s)]:
         ro_pulse_amp
Batched settable(s):
         freq
Batch size limit: 1024

Completed: 100%
 [ elapsed time: 00:04 | time left: 00:00 ]  last batch size: 400
[19]:
<xarray.Dataset> Size: 128kB
Dimensions:  (dim_0: 4000)
Coordinates:
    x0       (dim_0) float64 32kB 7.357e+09 7.357e+09 ... 7.367e+09 7.367e+09
    x1       (dim_0) float64 32kB 0.01 0.01 0.01 0.01 0.01 ... 0.5 0.5 0.5 0.5
Dimensions without coordinates: dim_0
Data variables:
    y0       (dim_0) float64 32kB nan nan nan nan nan ... nan nan nan nan nan
    y1       (dim_0) float64 32kB nan nan nan nan nan ... nan nan nan nan nan
Attributes:
    tuid:                             20250312-180353-831-1abfae
    name:                             resonator punchout
    grid_2d:                          True
    grid_2d_uniformly_spaced:         True
    1d_2_settables_uniformly_spaced:  False
    xlen:                             400
    ylen:                             10
[20]:
if cluster_ip is None:
    dh.set_datadir(Path.cwd() / "data")
    punchout_ds = load_dataset("20250204-234241-297-0b4720")
b2a = Basic2DAnalysis(dataset=punchout_ds)
b2a.run().display_figs_mpl()
../../../_images/applications_quantify_transmon_fixed_freq_transmon_29_0.png
../../../_images/applications_quantify_transmon_fixed_freq_transmon_29_1.png
../../../_images/applications_quantify_transmon_fixed_freq_transmon_29_2.png
../../../_images/applications_quantify_transmon_fixed_freq_transmon_29_3.png
[21]:
quantum_device.to_json_file("devices/")
[21]:
'devices/transmon_device_2025-03-12_18-04-03_UTC.json'

Qubit Spectroscopy#

Here we will carry out qubit spectroscopy on a single transmon in order to find the |0|1 drive frequency.

Qubit spectroscopy#

[22]:
def two_tone_spec_sched_nco(
    qubit,  # noqa: ANN001
    spec_pulse_frequencies: np.array,
    repetitions: int = 1,
) -> Schedule:
    """
    Generate a batched schedule for performing fast two-tone spectroscopy.

    Using the X gate to perform the frequency sweep on the qubit.

    Parameters
    ----------
    qubit
        qubit that should be used.
    spec_pulse_frequencies
        Sample frequencies for the spectroscopy pulse in Hertz.
    repetitions
        The amount of times the Schedule will be repeated.

    """
    sched = Schedule("two-tone", repetitions)
    sched.add_resource(ClockResource(name=qubit.name + ".01", freq=spec_pulse_frequencies.flat[0]))

    for acq_idx, spec_pulse_freq in enumerate(spec_pulse_frequencies):
        sched.add(Reset(qubit.name))
        sched.add(SetClockFrequency(clock=qubit.name + ".01", clock_freq_new=spec_pulse_freq))
        sched.add(X(qubit.name, freq=spec_pulse_freq))
        sched.add(Measure(qubit.name, acq_index=acq_idx), rel_time=200e-9)
    return sched
[23]:
freqs = ManualParameter(name="freq", unit="Hz", label="Frequency")
freqs.batched = True

qubit_spec_sched_kwargs = dict(
    qubit=qubit,
    spec_pulse_frequencies=freqs,
)

gettable = ScheduleGettable(
    quantum_device,
    schedule_function=two_tone_spec_sched_nco,
    schedule_kwargs=qubit_spec_sched_kwargs,
    real_imag=False,
    batched=True,
)

meas_ctrl.gettables(gettable)
[24]:
quantum_device.cfg_sched_repetitions(300)
center = 6.1e9
frequency_setpoints = np.linspace(center - 20e6, center + 20e6, 300)
meas_ctrl.settables(freqs)
meas_ctrl.setpoints(frequency_setpoints)

qs_ds = meas_ctrl.run("Two-tone")
qs_ds
Starting batched measurement...
Iterative settable(s) [outer loop(s)]:
         --- (None) ---
Batched settable(s):
         freq
Batch size limit: 300

Completed: 100%
 [ elapsed time: 00:00 | time left: 00:00 ]  last batch size: 300
[24]:
<xarray.Dataset> Size: 7kB
Dimensions:  (dim_0: 300)
Coordinates:
    x0       (dim_0) float64 2kB 6.08e+09 6.08e+09 ... 6.12e+09 6.12e+09
Dimensions without coordinates: dim_0
Data variables:
    y0       (dim_0) float64 2kB nan nan nan nan nan nan ... nan nan nan nan nan
    y1       (dim_0) float64 2kB nan nan nan nan nan nan ... nan nan nan nan nan
Attributes:
    tuid:                             20250312-180403-693-96bb79
    name:                             Two-tone
    grid_2d:                          False
    grid_2d_uniformly_spaced:         False
    1d_2_settables_uniformly_spaced:  False
[25]:
if cluster_ip is None:
    notebook_dir = Path.cwd()
    dh.set_datadir(notebook_dir / "data")
    qs_ds = load_dataset("20250204-234600-751-7ad233")
qs_analysis = QubitSpectroscopyAnalysis(dataset=qs_ds)
qs_analysis.run().display_figs_mpl()
../../../_images/applications_quantify_transmon_fixed_freq_transmon_37_0.png
[26]:
qubit.clock_freqs.f01(
    qs_analysis.quantities_of_interest["frequency_01"].nominal_value
)  # Store the qubit frequency.
print(f"{qubit.clock_freqs.f01():.6e}")
4.579629e+09
[27]:
quantum_device.to_json_file("devices/")
[27]:
'devices/transmon_device_2025-03-12_18-04-05_UTC.json'

Rabi Oscillations#

Here we will carry out an experiment to measure the Rabi frequency that is required to excite the qubit to |1.

Rabi Oscillations#

[28]:
pulse_amps = np.linspace(-0.14, 0.14, 200)
sched = Schedule("rabi_amplitude", 400)

for acq_idx, pulse_amp in enumerate(pulse_amps):
    sched.add(Reset(qubit.name))
    sched.add(X(qubit.name, amp180=pulse_amp))
    sched.add(Measure(qubit.name, acq_index=acq_idx), rel_time=20e-9)  # wait 20ns before measuring

rabi_ds = run_schedule(schedule=sched, quantum_device=quantum_device)
rabi_ds
[28]:
<xarray.Dataset> Size: 5kB
Dimensions:      (acq_index_0: 200)
Coordinates:
  * acq_index_0  (acq_index_0) int64 2kB 0 1 2 3 4 5 ... 194 195 196 197 198 199
Data variables:
    0            (acq_index_0) complex128 3kB (nan+nanj) ... (nan+nanj)
[29]:
if cluster_ip is None:
    notebook_dir = Path.cwd()
    dh.set_datadir(notebook_dir / "data")
    rabi_ds = load_dataset("20250205-003934-203-4f4791")

rabi_analysis = RabiAnalysis(tuid=rabi_ds.attrs["tuid"], dataset=rabi_ds)
rabi_analysis.run().display_figs_mpl()
../../../_images/applications_quantify_transmon_fixed_freq_transmon_44_0.png
[30]:
qubit.rxy.amp180(rabi_analysis.quantities_of_interest["Pi-pulse amplitude"].nominal_value)
print(f"Pi pulse amplitude: {qubit.rxy.amp180()}")
Pi pulse amplitude: 0.5430058585105821
[31]:
quantum_device.to_json_file("devices/")
[31]:
'devices/transmon_device_2025-03-12_18-04-06_UTC.json'

T1#

Here we measure the T1 decoherence time of the qubit.

T1#

[32]:
# This schedule can also be imported from quantify_scheduler.schedules.


def t1_sched(
    times: np.ndarray | float,
    qubit: str,
    repetitions: int = 1,
) -> Schedule:
    """
    Generate a schedule for performing a :math:`T_1` experiment.

    This measures the qubit relaxation time.

    Schedule sequence
        .. centered:: Reset -- pi -- Idle(tau) -- Measure

    See section III.B.2. of :cite:t:`krantz_quantum_2019` for an explanation of the Bloch-Redfield
    model of decoherence and the :math:`T_1` experiment.

    Parameters
    ----------
    times
        an array of wait times tau between the start of pi-pulse and the measurement.
    qubit
        the name of the device element e.g., :code:`"q0"` to perform the T1 experiment on.
    repetitions
        The amount of times the Schedule will be repeated.

    Returns
    -------
    :
        An experiment schedule.

    """
    device_element = qubit
    # ensure times is an iterable when passing floats.
    times = np.asarray(times)
    times = times.reshape(times.shape or (1,))

    schedule = Schedule("T1", repetitions)
    for i, tau in enumerate(times):
        schedule.add(Reset(device_element), label=f"Reset {i}")
        schedule.add(X(device_element), label=f"pi {i}")
        schedule.add(
            Measure(device_element, acq_index=i),
            ref_pt="start",
            rel_time=tau,
            label=f"Measurement {i}",
        )
    return schedule
[33]:
tau = ManualParameter(name="tau_delay", unit="s", label="Delay")
tau.batched = True

t1_sched_kwargs = {"qubit": qubit.name, "times": tau}

gettable = ScheduleGettable(
    quantum_device,
    schedule_function=t1_sched,
    schedule_kwargs=t1_sched_kwargs,
    real_imag=False,
    batched=True,
)
meas_ctrl.gettables(gettable)
[34]:
delay_setpoints = np.arange(40e-9, 200e-6, 500e-9)

meas_ctrl.settables(tau)
meas_ctrl.setpoints(delay_setpoints)

quantum_device.cfg_sched_repetitions(300)
t1_ds = meas_ctrl.run("T1 experiment")
t1_ds
Starting batched measurement...
Iterative settable(s) [outer loop(s)]:
         --- (None) ---
Batched settable(s):
         tau_delay
Batch size limit: 400

Completed: 100%
 [ elapsed time: 00:00 | time left: 00:00 ]  last batch size: 400
[34]:
<xarray.Dataset> Size: 10kB
Dimensions:  (dim_0: 400)
Coordinates:
    x0       (dim_0) float64 3kB 4e-08 5.4e-07 1.04e-06 ... 0.000199 0.0001995
Dimensions without coordinates: dim_0
Data variables:
    y0       (dim_0) float64 3kB nan nan nan nan nan nan ... nan nan nan nan nan
    y1       (dim_0) float64 3kB nan nan nan nan nan nan ... nan nan nan nan nan
Attributes:
    tuid:                             20250312-180406-769-ef541c
    name:                             T1 experiment
    grid_2d:                          False
    grid_2d_uniformly_spaced:         False
    1d_2_settables_uniformly_spaced:  False
[35]:
if cluster_ip is None:
    notebook_dir = Path.cwd()
    dh.set_datadir(notebook_dir / "data")
    t1_ds = load_dataset("20250205-203226-565-47a4c6")
t1_analysis = T1Analysis(dataset=t1_ds)
t1_analysis.run().display_figs_mpl()
../../../_images/applications_quantify_transmon_fixed_freq_transmon_53_0.png
[36]:
quantum_device.to_json_file("devices/")
[36]:
'devices/transmon_device_2025-03-12_18-04-08_UTC.json'

Ramsey Spectroscopy#

Here we demonstrate Ramsey Spectroscopy, which is used to tune the |0|1 drive frequency more precisely. Ramsey spectroscopy is also used to find T2.

Ramsey oscillations#

[37]:
# This schedule can also be imported from from quantify_scheduler.schedules


def ramsey_sched(
    times: np.ndarray | float,
    qubit: str,
    artificial_detuning: float = 0,
    repetitions: int = 1,
) -> Schedule:
    r"""
    Generate a schedule for performing a Ramsey experiment.

    This measures the dephasing time :math:`T_2^{\star}`.

    Schedule sequence
        .. centered:: Reset -- pi/2 -- Idle(tau) -- pi/2 -- Measure

    See section III.B.2. of :cite:t:`krantz_quantum_2019` for an explanation of the Bloch-Redfield
    model of decoherence and the Ramsey experiment.

    Parameters
    ----------
    times
        an array of wait times tau between the start of the first pi/2 pulse and
        the start of the second pi/2 pulse.
    artificial_detuning
        frequency in Hz of the software emulated, or ``artificial`` qubit detuning, which is
        implemented by changing the phase of the second pi/2 (recovery) pulse. The
        artificial detuning changes the observed frequency of the Ramsey oscillation,
        which can be useful to distinguish a slow oscillation due to a small physical
        detuning from the decay of the dephasing noise.
    qubit
        the name of the device element e.g., :code:`"q0"` to perform the Ramsey experiment on.
    repetitions
        The amount of times the Schedule will be repeated.

    Returns
    -------
    :
        An experiment schedule.

    """
    device_element = qubit
    # ensure times is an iterable when passing floats.
    times = np.asarray(times)
    times = times.reshape(times.shape or (1,))

    schedule = Schedule("Ramsey", repetitions)

    if isinstance(times, float):
        times = [times]

    for i, tau in enumerate(times):
        schedule.add(Reset(device_element), label=f"Reset {i}")
        schedule.add(X90(device_element))

        # the phase of the second pi/2 phase progresses to propagate
        recovery_phase = np.rad2deg(2 * np.pi * artificial_detuning * tau)
        schedule.add(
            Rxy(theta=90, phi=recovery_phase, qubit=device_element), ref_pt="start", rel_time=tau
        )
        schedule.add(Measure(device_element, acq_index=i), label=f"Measurement {i}")
    return schedule
[38]:
tau = ManualParameter(name="tau", unit="s", label="Time")
tau.batched = True

ramsey_sched_kwargs = {
    "qubit": qubit.name,
    "times": tau,
    "artificial_detuning": 0.0,
}

gettable = ScheduleGettable(
    quantum_device,
    schedule_function=ramsey_sched,
    schedule_kwargs=ramsey_sched_kwargs,
    real_imag=False,
    batched=True,
)
meas_ctrl.gettables(gettable)
[39]:
tau_setpoints = np.arange(20e-9, 4e-6, 32e-9)

meas_ctrl.settables(tau)
meas_ctrl.setpoints(tau_setpoints)

quantum_device.cfg_sched_repetitions(500)
ramsey_ds = meas_ctrl.run("ramsey")
ramsey_ds
Starting batched measurement...
Iterative settable(s) [outer loop(s)]:
         --- (None) ---
Batched settable(s):
         tau
Batch size limit: 125

Completed: 100%
 [ elapsed time: 00:00 | time left: 00:00 ]  last batch size: 125
[39]:
<xarray.Dataset> Size: 3kB
Dimensions:  (dim_0: 125)
Coordinates:
    x0       (dim_0) float64 1kB 2e-08 5.2e-08 8.4e-08 ... 3.956e-06 3.988e-06
Dimensions without coordinates: dim_0
Data variables:
    y0       (dim_0) float64 1kB nan nan nan nan nan nan ... nan nan nan nan nan
    y1       (dim_0) float64 1kB nan nan nan nan nan nan ... nan nan nan nan nan
Attributes:
    tuid:                             20250312-180408-712-65f4c7
    name:                             ramsey
    grid_2d:                          False
    grid_2d_uniformly_spaced:         False
    1d_2_settables_uniformly_spaced:  False
[40]:
if cluster_ip is None:
    notebook_dir = Path.cwd()
    dh.set_datadir(notebook_dir / "data")
    ramsey_ds = load_dataset("20250206-230423-264-3c48f3")
ramsey_analysis = RamseyAnalysis(ramsey_ds)
ramsey_analysis.run(
    artificial_detuning=ramsey_sched_kwargs["artificial_detuning"]
).display_figs_mpl()
../../../_images/applications_quantify_transmon_fixed_freq_transmon_61_0.png
[41]:
detuning = ramsey_analysis.quantities_of_interest["detuning"].nominal_value
qubit.clock_freqs.f01(qubit.clock_freqs.f01() - detuning)
print(f"New qubit frequency: {qubit.clock_freqs.f01} Hz")
New qubit frequency: q0_clock_freqs_f01 Hz
[42]:
quantum_device.to_json_file("devices/")
[42]:
'devices/transmon_device_2025-03-12_18-04-10_UTC.json'

Discriminated Single Shot Readout#

Here we show how to run a readout calibration experiment and fit a discriminator with a linear discriminant analysis. This experiment is sometimes called multi-state discrimination.

Schedule definition#

[43]:
def readout_calibration_sched(
    qubit: str,
    prepared_states: list[int],
    repetitions: int = 1,
    acq_protocol: Literal[
        "SSBIntegrationComplex", "ThresholdedAcquisition"
    ] = "SSBIntegrationComplex",
) -> Schedule:
    """
    Make a schedule for readout calibration.

    Parameters
    ----------
    qubit
        The name of the qubit e.g., :code:`"q0"` to perform the experiment on.
    prepared_states
        A list of integers indicating which state to prepare the qubit in before measuring.
        The ground state corresponds to 0 and the first-excited state to 1.
    repetitions
        The number of times the schedule will be repeated. Fixed to 1 for this schedule.
    acq_protocol
        The acquisition protocol used for the readout calibration. By default
        "SSBIntegrationComplex", but "ThresholdedAcquisition" can be
        used for verifying thresholded acquisition parameters.

    Returns
    -------
    :
        An experiment schedule.

    Raises
    ------
    NotImplementedError
        If the prepared state is > 1.

    """
    schedule = Schedule(f"Readout calibration {qubit}", repetitions=1)

    for i, prep_state in enumerate(prepared_states):
        schedule.add(Reset(qubit), label=f"Reset {i}")
        if prep_state == 0:
            pass
        elif prep_state == 1:
            schedule.add(Rxy(qubit=qubit, theta=180, phi=0))
        else:
            raise NotImplementedError(
                "Preparing the qubit in the higher excited states is not supported yet."
            )
        schedule.add(
            Measure(qubit, acq_index=i, bin_mode=BinMode.APPEND, acq_protocol=acq_protocol),
            label=f"Measurement {i}",
        )
    return schedule

SSRO with single side band (SSB) integration#

[44]:
states = ManualParameter(name="states", unit="", label="Prepared state")
states.batch_size = 400
states.batched = True

readout_calibration_sched_kwargs = dict(
    qubit=qubit.name, prepared_states=states, acq_protocol="SSBIntegrationComplex"
)

# set gettable
ssro_gettable = ScheduleGettable(
    quantum_device,
    schedule_function=readout_calibration_sched,
    schedule_kwargs=readout_calibration_sched_kwargs,
    real_imag=True,
    batched=True,
)

# set measurement control
meas_ctrl.gettables(ssro_gettable)
[45]:
num_shots = 1000
state_setpoints = np.asarray([0, 1] * num_shots)

# replace the get method for the gettable in case the cluster is a dummy
if "dummy" in str(cluster._transport):
    from simulated_data import get_simulated_ssro_data

    ssro_gettable.get = partial(get_simulated_ssro_data, num_shots=num_shots)

meas_ctrl.settables(states)
meas_ctrl.setpoints(state_setpoints)

ssro_ds = dh.to_gridded_dataset(meas_ctrl.run("Single shot readout experiment"))
ssro_ds
Starting batched measurement...
Iterative settable(s) [outer loop(s)]:
         --- (None) ---
Batched settable(s):
         states
Batch size limit: 400

Completed: 100%
 [ elapsed time: 00:00 | time left: 00:00 ]  last batch size: 400
[45]:
<xarray.Dataset> Size: 48kB
Dimensions:  (x0: 2000)
Coordinates:
  * x0       (x0) int64 16kB 0 1 0 1 0 1 0 1 0 1 0 1 ... 0 1 0 1 0 1 0 1 0 1 0 1
Data variables:
    y0       (x0) float64 16kB 0.02724 0.03581 -0.007418 ... -0.001242 0.0332
    y1       (x0) float64 16kB 0.01264 0.00428 0.02087 ... 0.007679 0.03984
Attributes:
    tuid:                             20250312-180410-135-f6bc40
    name:                             Single shot readout experiment
    grid_2d:                          False
    grid_2d_uniformly_spaced:         False
    1d_2_settables_uniformly_spaced:  False

Fit line discriminator with linear discriminant analysis (LDA)#

[46]:
ssro_analysis = ReadoutCalibrationAnalysis(tuid=dh.get_latest_tuid())
ssro_analysis.run().display_figs_mpl()
../../../_images/applications_quantify_transmon_fixed_freq_transmon_72_0.png
[47]:
fit_results = ssro_analysis.fit_results["linear_discriminator"].params
acq_threshold = fit_results["acq_threshold"].value
acq_rotation = (np.rad2deg(fit_results["acq_rotation_rad"].value)) % 360

qubit.measure.acq_threshold(acq_threshold)
qubit.measure.acq_rotation(acq_rotation)

SSRO with thresholded acquisition#

[48]:
disc_ssro_gettable_kwargs = dict(
    qubit=qubit.name, prepared_states=states, acq_protocol="ThresholdedAcquisition"
)

# set gettable
disc_ssro_gettable = ScheduleGettable(
    quantum_device,
    schedule_function=readout_calibration_sched,
    schedule_kwargs=disc_ssro_gettable_kwargs,
    real_imag=True,
    batched=True,
)

# set measurement control
meas_ctrl.gettables(disc_ssro_gettable)
[49]:
num_shots = 10_000
state_setpoints = np.asarray([0, 1] * num_shots)

# replace the get method for the gettable in case the cluster is a dummy
if "dummy" in str(cluster._transport):
    from simulated_data import get_simulated_binary_ssro_data

    def dummy_gettable():
        disc_ssro_gettable.initialize()
        return get_simulated_binary_ssro_data(num_shots)

    disc_ssro_gettable.get = dummy_gettable

meas_ctrl.settables(states)
meas_ctrl.setpoints(state_setpoints)

disc_ssro_ds = dh.to_gridded_dataset(meas_ctrl.run("Discriminated single shot readout experiment"))
disc_ssro_ds
Starting batched measurement...
Iterative settable(s) [outer loop(s)]:
         --- (None) ---
Batched settable(s):
         states
Batch size limit: 400

Completed: 100%
 [ elapsed time: 00:00 | time left: 00:00 ]  last batch size: 400
[49]:
<xarray.Dataset> Size: 480kB
Dimensions:  (x0: 20000)
Coordinates:
  * x0       (x0) int64 160kB 0 1 0 1 0 1 0 1 0 1 0 1 ... 1 0 1 0 1 0 1 0 1 0 1
Data variables:
    y0       (x0) float64 160kB 1.0 1.0 0.0 1.0 0.0 1.0 ... 1.0 0.0 1.0 0.0 1.0
    y1       (x0) float64 160kB nan nan nan nan nan nan ... nan nan nan nan nan
Attributes:
    tuid:                             20250312-180411-352-43bbcc
    name:                             Discriminated single shot readout exper...
    grid_2d:                          False
    grid_2d_uniformly_spaced:         False
    1d_2_settables_uniformly_spaced:  False
[50]:
ConfusionMatrixDisplay.from_predictions(disc_ssro_ds.x0.data, disc_ssro_ds.y0.data)
plt.title("Confusion Matrix")
plt.xlabel("Measured State")
plt.ylabel("Prepared State")
[50]:
Text(0, 0.5, 'Prepared State')
../../../_images/applications_quantify_transmon_fixed_freq_transmon_77_1.png
[51]:
quantum_device.to_json_file("devices/")
[51]:
'devices/transmon_device_2025-03-12_18-04-12_UTC.json'