See also

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

image0

[1]:
from __future__ import annotations

from pathlib import Path

import numpy as np
import rich  # noqa:F401
from qcodes.parameters import ManualParameter
from simulated_data import get_simulated_res_spec_data

import quantify_core.data.handling as dh
from quantify_core.analysis.base_analysis import Basic2DAnalysis
from quantify_core.analysis.spectroscopy_analysis import ResonatorSpectroscopyAnalysis
from quantify_core.data.handling import load_dataset
from quantify_scheduler import QuantumDevice, Schedule, ScheduleGettable
from quantify_scheduler.device_under_test.transmon_element import BasicTransmonElement
from quantify_scheduler.enums import BinMode
from quantify_scheduler.operations import IdlePulse, Measure
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"
[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)

Resonator Spectroscopy#

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

Setup#

In this section we configure the hardware configuration which specifies the connectivity of our system.

The experiments of this tutorial are meant to be executed with a Qblox Cluster controlling a transmon system. The experiments can also be executed using a dummy Qblox device that is created via an instance of the Cluster class, and is initialized with a dummy configuration. When using a dummy device, the analysis will not work because the experiments will return np.nan values.

Configuration file#

This is a template hardware configuration file for a 2-qubit system with a flux-control line which can be used to tune the qubit frequency. We will only work with qubit 0.

The hardware connectivity is as follows, by cluster slot:

  • QCM (Slot 2)

    • \(\text{O}^{1}\): Flux line for q0.

    • \(\text{O}^{2}\): Flux line for q1.

  • QCM-RF (Slot 6)

    • \(\text{O}^{1}\): Drive line for q0 using fixed 80 MHz IF.

    • \(\text{O}^{2}\): Drive line for q1 using fixed 80 MHz IF.

  • QRM-RF (Slot 8)

    • \(\text{O}^{1}\) and \(\text{I}^{1}\): Shared readout line for q0/q1 using a fixed LO set at 7.5 GHz.

Note that in the hardware configuration below the mixers are uncorrected, but for high fidelity experiments this should also be done for all the modules.

Quantum device settings#

Here we initialize our QuantumDevice and our qubit parameters, checkout this tutorial for further details.

In short, a QuantumDevice contains device elements where we save our found parameters. Here we are loading a template for 2 qubits, but we will only use qubit 0.

[5]:
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#

[6]:
# 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
[7]:
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)
[8]:
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

[8]:
<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.17 13.44 13.25 13.23 ... 14.97 15.03 14.88
    y1       (dim_0) float64 2kB -134.9 -130.7 -124.1 ... 7.038 12.15 17.46
Attributes:
    tuid:                             20250312-180329-116-9bd3d0
    name:                             resonator spectroscopy
    grid_2d:                          False
    grid_2d_uniformly_spaced:         False
    1d_2_settables_uniformly_spaced:  False
[9]:
rs_analysis = ResonatorSpectroscopyAnalysis(
    dataset=rs_ds, settings_overwrite={"mpl_transparent_background": False}
)
rs_analysis.run().display_figs_mpl()
../../../_images/applications_quantify_transmon_resonator_spec_14_0.png
../../../_images/applications_quantify_transmon_resonator_spec_14_1.png
../../../_images/applications_quantify_transmon_resonator_spec_14_2.png
[10]:
qubit.clock_freqs.readout(rs_analysis.quantities_of_interest["fr"].nominal_value)

Resonator punchout#

[11]:
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)
[12]:
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

[12]:
<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-180333-013-d332fe
    name:                             resonator punchout
    grid_2d:                          True
    grid_2d_uniformly_spaced:         True
    1d_2_settables_uniformly_spaced:  False
    xlen:                             400
    ylen:                             10
[13]:
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_resonator_spec_19_0.png
../../../_images/applications_quantify_transmon_resonator_spec_19_1.png
../../../_images/applications_quantify_transmon_resonator_spec_19_2.png
../../../_images/applications_quantify_transmon_resonator_spec_19_3.png
[14]:
inst_coord.last_schedule.compiled_instructions
[15]:
display_dict(quantum_device.hardware_config())
[15]:
[16]:
quantum_device.to_json_file("devices/")
[16]:
'devices/transmon_device_2025-03-12_18-03-43_UTC.json'