# Pulsed Qubit Spectroscopy

After we have determined the resonance frequency of the readout resonator, in this next step we will use this information to determine the qubit resonance frequency. We will make use of the fact that the resonance frequency of the readout resonator does depend on the quantum state of the qubit, since the two are coupled. For this qubit spectroscopy experiment, we will now also use the drive line of the qubit. On this line we will play a long excitation pulse whose frequency we will sweep. After playing the qubit excitation pulse, we will use the previously calibrated readout resonator frequency to probe the response of the resonator. If the qubit excitation pulse was off-resonant, the qubit state will not change, and the readout resonator’s resonance frequency will be the same as without the qubit drive. However, if the qubit excitation pulse is resonant with the qubit, the qubit will be excited, the readout resonator frequency will shift, and with it the amplitude of the readout response.

Again, we start by defining parameters of the experiment, such as the frequency range we want to sweep over, the number of sweep points, the number of averages per point and the sweep parameters. We chose the qubit excitation pulse to be a simple square pulse with length and amplitude defined in the qubit parameters dictionary. Similarly, we define a readout weighting function, which is used internally in the UHFQA when integrating the return signal from the resonator, and can be used to optimize the readout fidelity for a given readout pulse length as is for example described here. For simplicity, here we restrict ourselves to simple square weights of unit amplitude.

# frequency range of spectroscopy scan
qspec_range = 100e6
# how many frequency points to measure
qspec_num = 51

# how many averages per point: 2^n_average
n_average = 10

# set up sweep parameters - qubit drive frequency
freq_sweep_q0 = LinearSweepParameter(uid="freq-qubit", start=qubit_parameters['qb0_freq'] - qspec_range/2, stop=qubit_parameters['qb0_freq'] + qspec_range/2, count=qspec_num)
freq_sweep_q1 = LinearSweepParameter(uid="freq-qubit", start=qubit_parameters['qb1_freq'] - qspec_range/2, stop=qubit_parameters['qb1_freq'] + qspec_range/2, count=qspec_num)

# square pulse to excite the qubit
square_pulse = pulse_library.const(
uid="const_iq", length=qubit_parameters['qb_len_spec'], amplitude=qubit_parameters['qb_amp_spec']
)
)
# integration weights for qubit measurement
)

Since all following experiments will use a similar structure for the qubit readout, we define a function for this purpose, which can be reused later and with different parameters, if needed. From here on, we use the integration mode of the UHFQA, which is the standard qubit readout mode and allows for multiplexed readout of up to 10 qubits on the same line.

# qubit readout and signal acquisition
# readout pulse and data acquisition
with exp.section(uid=section_id):
# reserve section for time of pulses played on signal reserve_id
if reserve_id is not None:
exp.reserve(signal=reserve_id)
# add an offset between the readout pulse and the start of  the data acquisition
if acquire_offset is not None:
exp.delay(signal=acquire_id, time=acquire_offset)
# signal data acquisition
exp.acquire(
signal=acquire_id,
handle=acquire_handle,
)
with exp.section(uid="delay"):
# relax time after readout - for signal processing and qubit relaxation to groundstate
if reserve_id is not None:
exp.reserve(signal=reserve_id)

As in the previous resonator spectroscopy experiment, we have determined the proper readout frequency for each qubit, we are now also setting this frequency to the oscillators modulating the readout and acquisition logical signal lines. Using the previously defined shortcuts to the logical signals, this modifies the calibration of the device setup, and will have effect for all subsequent experiments. Note that we also change the oscillator type to software, which in principle allows multiplexed readout of multiple qubits at different frequencies, as shown here.

## configure oscillator settings for readout and acquisition - defined on device setup as persistent baseline
acquire_Oscillator_q0.frequency = qubit_parameters['ro_freq_q0']
acquire_Oscillator_q0.modulation_type = ModulationType.SOFTWARE

acquire_Oscillator_q1.frequency = qubit_parameters['ro_freq_q1']
acquire_Oscillator_q1.modulation_type = ModulationType.SOFTWARE

Finally, we define the qubit spectroscopy experiment. Here, we use a qubit drive line, a readout excitation line and a signal acquisition line.

For qubit spectroscopy, we sweep the qubit drive frequency, by assigning a variable sweep parameter to the frequency of the signal line oscillator. This parameter is also iterated in the outer, near-time loop of the experimental pulse sequence. For each frequency sweep point, we average multiple single shots in a real-time inner loop. In the pulse sequence, we play the qubit excitation pulse with varying frequency, followed by the readout and signal acquisition part.

def qubit_spectroscopy(freq_sweep):
# Create qubit spectroscopy Experiment
exp_qspec = Experiment(
uid="Qubit Spectroscopy",
signals=[
ExperimentSignal("drive"),
ExperimentSignal("measure"),
ExperimentSignal("acquire"),
]
)

## define experimental pulse sequence
# outer loop - near time qubit drive frequency sweep
with exp_qspec.sweep(uid="qfreq_sweep", parameter=freq_sweep, execution_type=ExecutionType.NEAR_TIME):
# inner loop - real-time averaging
with exp_qspec.acquire_loop_rt(uid="qfreq_shots", count=pow(2, n_average), acquisition_type=AcquisitionType.INTEGRATION):
# qubit drive
with exp_qspec.section(uid="qubit_excitation"):
exp_qspec.play(signal="drive", pulse=square_pulse)
# readout pulse and data acquisition
acquire_id="acquire", reserve_id="drive", acquire_handle="q0_spec",
)

return exp_qspec

As for resonator spectroscopy, we define calibration objects to apply to the experiment when using different qubits. Here the calibration modifies the digital oscillator settings for the qubit drive line, setting the frequency sweep as well as hardware modulation. The signal map now also contains a reference to the qubit drive line, but otherwise is similar to the resonator spectroscopy case.

# calibration for qubit 0
exp_calibration_q0 = Calibration()
exp_calibration_q0["drive"] = SignalCalibration(oscillator = Oscillator(
frequency=freq_sweep_q0,
modulation_type=ModulationType.HARDWARE,
),
)
# signal map for qubit 0
q0_map = {"drive": "/logical_signal_groups/q0/drive_line",
"measure": "/logical_signal_groups/q0/measure_line",
"acquire": "/logical_signal_groups/q0/acquire_line",
}

As before, the experiment is executed on the session, and the results are accessed through the results object provided by the session after successful execution.

Figure 1 shows example data as can be expected from a qubit spectroscopy experiment. Similar to the resonator spectroscopy, we show the absolute value of the readout resonator response as a function of the qubit drive excitation frequency. We see a change in the response when the qubit drive is in resonance with the qubit transition — the qubit is getting excited and the apparent resonance frequency of the resonator changes. In the example notebook, we use the fit to the qubit spectroscopy data to extract the resonance frequency of the qubit transition, which we adjust in the qubit parameter dictionary.

Figure 1. Example data from qubit spectroscopy - courtesy Dr. Daniel J. Weigand, PGI13, FZ Jülich

With knowledge of the qubit transition frequency, we next will measure the power needed to drive Rabi oscillations, with the aim to calibrate arbitrary rotations of the qubit state vector on the Bloch sphere.