# Continuous Resonator Spectroscopy

 This tutorial is applicable to all SHFQA Instruments.

## Goals and Requirements

The goal of this tutorial is to demonstrate how to use the SHFQA to perform continuous resonator spectroscopy measurement using Zurich Instrument Toolkit API.

 Users can download all zhinst-toolkit example files introduced in this tutorial from GitHub, https://github.com/zhinst/zhinst-toolkit/tree/main/examples.

## Preparation

The tutorial starts with the Instrument in the default configuration (e.g., after a power cycle). For an optimal tutorial experience, please follow these preparation steps:

• ensure that the version of the LabOne Python API, LabOne and the Firmware of the SHFQA, zhinst-toolkit (pip install zhinst-toolkit) and Python (3.7 or newer) are updated and compatible,

• make sure that the Instrument is powered on and connected by Ethernet to your local area network (LAN) where the host computer resides or by USB (Maintenance port) to the host computer,

• start LabOne and open the LabOne Graphical User Interface using the default web browser,

• connect the SHFQA channel 1 output (input) to the readout input (output) line, see SHFQA connection.

Figure 1. SHFQA connection.

## Tutorial

The tutorial starts with configuring the Sweeper, and is followed by sending out continuous wave and integrating the frequency down-converted signal, and is finished with power and phase plots calculated from integration results.

Users can use the Python code below to connect the instrument, perform continuous wave resonator spectroscopy, and each step is explained as the following.

1. Connect the Instrument

Create a toolkit session to the data server and connect the Instrument with the device ID, e.g. 'DEV12001', see Connecting to the Instrument.

# Load the LabOne API and other necessary packages
from zhinst.toolkit import Session

DEVICE_ID = 'DEVXXXXX'
SERVER_HOST = 'localhost'

session = Session(SERVER_HOST)              ## connect to data server
device = session.connect_device(DEVICE_ID)  ## connect to device
2. Create a Sweeper and configure it

Python API SHFSweeper class is the core of the spectroscopy measurements. It defines all relevant parameters for frequency sweeping and sequencing. Toolkit wraps around the SHFSweeper and exposes an interface that is similar to the LabOne modules, meaning the parameters are exposed in a node tree like structure.

The sweep parameters are configured such that the offset frequency of output signal is linearly swept over 1000 points from -1 GHz to 1 GHz around the center frequency of 5 GHz with the input and output power range of 0 dBm and the oscillator gain of 0.8, the input signal is integrated for 100 μs after receiving an internal triggered, and the measurement is repeated 200 times at each offset frequency.

There are 2 sweeping modes, sequencer-based mode (default) and host-driven mode. The sequencer-based mode is recommended and used in this tutorial. In the sequencer-based mode, the frequency sweep is done by updating the frequency of the digital oscillator via the SeqC commands configFreqSweep and setSweepStep. This mode allows a fast resonator spectroscopy measurement with estimated running time of $$(t_{\mathrm{int}} + 200 ns)n_{\mathrm{averages}}n_{\mathrm{points}}$$, where $$n_{\mathrm{averages}}$$ is the number of averages, $$n_{\mathrm{points}}$$ is the number of sweep points. In the host-driven mode, the frequency sweep is done by updating the frequency of the digital oscillator via software, therefore measurement speed is not fast. Logarithmic or other non-linear frequency sweep is supported by the host-driven sweep mode only.

To get a better signal-to-noise ratio (SNR), the measurement is repeated, and the result is averaged. There are 2 ways for averaging, sequential and cyclic. In the sequential mode, measurement is repeated after each frequency step. In the cyclic mode, measurement is repeated after sweeping through all frequencies.

The output frequency of the signal is the sum of the center frequency and the offset frequency. The output power of the signal is determined by the output power range and the oscillator gain, see Inputs/Outputs Tab. The input range should be set properly to avoid input overflow and to reach the best SNR.

sweeper = session.modules.shfqa_sweeper
sweeper.device(device)
CHANNEL_INDEX = 0 # physical Channel 1

sweeper.sweep.start_freq(-1000e6) # in units of Hz
sweeper.sweep.stop_freq(1000e6) # in units of Hz
sweeper.sweep.num_points(1000)
sweeper.sweep.oscillator_gain(0.8) # amplitude scaling factor, 0 to 1
sweeper.sweep.use_sequencer = True # True (recommended): sequencer-based sweep; False: host-driven sweep

sweeper.average.integration_time(100e-6) # in units of second
sweeper.average.num_averages(200)
sweeper.average.mode("sequential") # "sequential" or "cyclic" averaging

sweeper.rf.channel(CHANNEL_INDEX)
sweeper.rf.center_freq(5e9) # in units of Hz
sweeper.rf.input_range(0) # in units of dBm
sweeper.rf.output_range(0) # in units of dBm

with device.set_transaction():
device.qachannels[CHANNEL_INDEX].input.on(1)
device.qachannels[CHANNEL_INDEX].output.on(1)
3. Run the measurement and plot the data

After executing sweeper.run(), all above parameters are updated, and a SeqC program is automatically generated, uploaded and compiled based on the sweep parameters, see in Figure 2. In the program, ConfigFreqSweep sets the start frequency and the frequency increment in units of Hz for a chosen oscillator, and setSweepStep sets the oscillator frequency. The oscillator phase is reset by resetOscPhase before each measurement. The trigger generated in the sequencer is used to start the integration, and the playZero sets the cycle duration. The measurement is repeated using the nested for loop according to the averaging mode. The measurement starts after enabling the sequencer.

The result returned from sweeper.run() is complex data $$E_{\mathrm{sweeper}}$$ in units of Vrms. It is averaged and normalized by the integration length. The power $$P$$ and phase $$\phi$$ can be calculated as,

\tag{1} \begin{aligned} P & = 10\lg(\frac{|E_{\mathrm{sweeper}}|^2}{R}1000),\\ \phi & = \arctan\frac{\Im(E_{\mathrm{sweeper}})}{\Re(E_{\mathrm{sweeper}})}, \end{aligned}

where $$R$$ is 50 $$\Omega$$, 1000 is the conversion factor from W to mW. With sweeper.plot(), the power and phase are calculated and plotted, see Figure 3.

result = sweeper.run()
num_points_result = len(result["vector"])
print(f"Measured at {num_points_result} frequency points.")
sweeper.plot()
Figure 2. Continuous spectroscopy measurement seqc program in the Readout Pulse Generator Tab.
Figure 3. Continuous spectroscopy measurement results in loopback configuration.