Spectroscopy workflow¶
[!WARNING] Operations in this notebook change control-instrument settings, including LO and NCO values. Running them can affect other users sharing the same hardware, so use this notebook only when that impact is acceptable.
This notebook walks through a full spectroscopy bring-up sequence for one multiplexed readout group: locate the resonators, refine their readout frequencies, identify the qubit transitions, and finish by updating the default control amplitudes. It assumes you will inspect the plots, edit the configuration files manually where noted, and reload before continuing.
1. Create an Experiment¶
Start from the mux you want to characterize, then load the corresponding configuration and parameter files for your system.
import numpy as np
import qubex as qx
exp = qx.Experiment(
system_id="YOUR_SYSTEM_ID",
muxes=[0],
# config_dir="/path/to/qubex-config/config",
# params_dir="/path/to/qubex-config/params/YOUR_SYSTEM_ID",
)
2. Connect to the setup¶
Connecting also synchronizes the clocks, so do this before running any spectroscopy measurement.
# Clocks will be synced when connected
exp.connect()
3. Run a coarse resonator scan¶
Begin with a broad readout-frequency sweep on one representative qubit to identify the frequency region that covers all resonators in the mux. Replace the placeholder range below with one wide enough to include every expected resonator frequency in your device.
# Sweep readout frequency to locate the resonator band
# Set the readout amplitude to a reasonable value
exp.scan_resonator_frequencies(
exp.qubit_labels[0], # Qubit label for the associated resonator
frequency_range=np.arange(9.75, 10.75, 0.002), # Placeholder example in GHz
readout_amplitude=0.1,
save_image=True,
)
4. Sweep readout frequency and power¶
Use the coarse scan to define a narrower window, then build a 2D resonator spectroscopy map to choose a practical readout power. Convert the selected power into the amplitude used by the lower-level scan APIs.
# Choose a frequency range that covers all resonator frequencies
readout_frequency_range = np.arange(10.05, 10.55, 0.002)
# Sweep readout frequency and power
exp.resonator_spectroscopy(
exp.qubit_labels[0],
frequency_range=readout_frequency_range,
power_range=np.arange(-60, 5, 5),
)
# Choose a readout power and convert it to amplitude
readout_power = -40 # dB
readout_amplitude = 10 ** (readout_power / 20)
readout_amplitude
5. Re-scan the resonators and map the peaks¶
With the readout amplitude fixed, repeat the resonator scan, extract the fitted peaks, and assign those peak frequencies back to qubit labels in the chip-specific order for this mux.
# Re-scan readout frequency with the selected power
result = exp.scan_resonator_frequencies(
exp.qubit_labels[0],
frequency_range=readout_frequency_range,
readout_amplitude=readout_amplitude,
save_image=True,
)
# Extract the fitted peaks
peaks = result["peaks"]
# Map resonator frequencies to qubit labels
# The ordering depends on the chip design
resonator_frequencies = {
exp.qubit_labels[0]: peaks[1],
exp.qubit_labels[1]: peaks[3],
exp.qubit_labels[2]: peaks[2],
exp.qubit_labels[3]: peaks[0],
}
resonator_frequencies
6. Persist and refine the resonator frequencies¶
Write the coarse resonator frequencies to readout_frequency.yaml, reload, and then measure the reflection coefficient for each qubit to extract a more precise fitted resonator frequency. After updating readout_frequency.yaml again, reload once more before moving to qubit spectroscopy.
# Update `readout_frequency.yaml` manually, then reload
exp.reload()
# Fit the reflection coefficient at resonance
fine_resonator_frequencies = {}
for qubit in exp.qubit_labels:
result = exp.measure_reflection_coefficient(
qubit,
readout_amplitude=readout_amplitude,
)
fine_resonator_frequencies[qubit] = result["f_r"]
fine_resonator_frequencies
# Update `readout_frequency.yaml` again, then reload
exp.reload()
7. Find rough qubit frequencies¶
Run a first-pass control-frequency sweep for each qubit, then define a per-qubit frequency window that covers the ge and ef transitions you want to inspect more closely.
# Sweep control frequency to find the qubit frequency
# The qubit frequency is ac-Stark shifted by the photon number in the resonator,
# so keep `readout_amplitude` as low as possible while the signal remains visible
for qubit in exp.qubit_labels:
exp.scan_qubit_frequencies(
qubit,
control_amplitude=0.1,
readout_amplitude=0.01,
)
# Choose a control-frequency range that covers the `ge` and `ef` transitions
control_frequency_ranges = {
exp.qubit_labels[0]: np.arange(6.5, 7.5, 0.005),
exp.qubit_labels[1]: np.arange(7.5, 8.5, 0.005),
exp.qubit_labels[2]: np.arange(7.5, 8.5, 0.005),
exp.qubit_labels[3]: np.arange(6.5, 7.7, 0.005),
}
8. Sweep qubit frequency and fit the resonance¶
Use a 2D qubit spectroscopy scan to choose the operating region, measure the resonance for each qubit, and then update control_frequency.yaml with the fitted qubit_frequency values before reloading.
# Sweep control frequency and power
# The qubit frequency is ac-Stark shifted by the photon number in the resonator,
# so keep `readout_amplitude` as low as possible while the signal remains visible
for qubit in exp.qubit_labels:
exp.qubit_spectroscopy(
qubit,
frequency_range=control_frequency_ranges[qubit],
power_range=np.arange(-60, 5, 5),
readout_amplitude=0.01,
)
# Fit the qubit frequency at resonance
for qubit in exp.qubit_labels:
exp.measure_qubit_resonance(
qubit,
frequency_range=control_frequency_ranges[qubit],
readout_amplitude=0.03,
)
# Update `control_frequency.yaml` manually, then reload
exp.reload()
9. Validate the waveform and update control amplitudes¶
After the frequency updates, check the readout waveform, obtain a Rabi fit, and convert that result into default control amplitudes. Once you edit control_amplitude.yaml, reload so the final validation uses the new values.
# Check the readout-pulse waveform
exp.check_waveform()
# Measure the Rabi oscillation
exp.obtain_rabi_params()
# Calculate the default control amplitudes
exp.calc_control_amplitudes()
# Update `control_amplitude.yaml` manually, then reload
exp.reload()
10. Re-check the Rabi oscillation¶
Run the Rabi measurement again to confirm the updated default amplitudes behave as expected.
# Re-check the Rabi oscillation
exp.obtain_rabi_params()