See also
A Jupyter notebook version of this tutorial can be downloaded here
.
[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()



[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()




[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'