# Pulsed Resonator Spectroscopy

 This tutorial is applicable to all SHFQC Instruments.

## Goals and Requirements

The goal of this tutorial is to demonstrate how to use SHFQC to perform pulsed spectroscopy measurement with a customized pulse envelope using Zurich Instruments 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. It is possible that some tutorials are written for the SHFQA or SHFSG. This is not a concern as they can be run also on the SHFQC.

## 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 SHFQC device, 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 SHFQC Quantum Analyzer channel output (input) to the readout input (output) line, see SHFQC connection.

Figure 1. SHFQC connection.

## Tutorial

The tutorial starts with configuring sweep parameters is followed by generating and uploading pulse envelope and run the measurement, is finished with verification of the spectroscopy pulse.  Users can use the Python code below to perform pulsed spectroscopy measurement, and each step is explained as the following.

1. Connect the instrument

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

# Load the LabOne API and other necessary packages
from zhinst.toolkit import Session
from scipy.signal import gaussian
import numpy as np

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

Create a Sweeper and configure sweep parameters, see tutorial Continuous Resonator Spectroscopy.

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(1001)
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(1e-6) # in units of second
sweeper.average.num_averages(200)
sweeper.average.mode("sequential") # "sequential" or "cyclic"

sweeper.rf.channel(CHANNEL_INDEX)
sweeper.rf.center_freq(7e9) # 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. Generate and upload pulse envelope

Create a complex flat-top Gaussian envelope with 1 μs duration and 50 ns rise and fall time. Enable the pulsed mode by sweeper.envelope.enable(True) and upload the envelope to the waveform memory. This envelope can be displayed on the Waveform Viewer of the Readout Pulse Generator.

SAMPLING_FREQUENCY = 2e9 # in units of Hz
ENVELOPE_DURATION = 1.0e-6 # in units of second
ENVELOPE_RISE_FALL_TIME = 0.05e-6 # in units of second

rise_fall_len = int(ENVELOPE_RISE_FALL_TIME * SAMPLING_FREQUENCY)
std_dev = rise_fall_len // 10
gauss = gaussian(2 * rise_fall_len, std_dev)
complex_amplitude = (1 - 1j)/np.sqrt(2)
flat_top_gaussian = np.ones(int(ENVELOPE_DURATION * SAMPLING_FREQUENCY)) * complex_amplitude
flat_top_gaussian[0:rise_fall_len] = gauss[0:rise_fall_len] * complex_amplitude
flat_top_gaussian[-rise_fall_len:] = gauss[-rise_fall_len:] * complex_amplitude
sweeper.average.integration_delay(220e-9) # in units of second
sweeper.envelope.enable(True) # True: Pulsed mode; False: Continuous mode
sweeper.envelope.waveform(flat_top_gaussian) # upload envelope waveform
4. Run the measurement and plot the data

After executing sweeper.run(), all above parameters are updated, a SeqC program is automatically generated, uploaded and compiled based on the sweep parameters, see Continuous Resonator Spectroscopy, and the result is downloaded after the measurement is done. The power and phase are calculated (see Continuous Resonator Spectroscopy.) and plotted, see in Figure 3.

result = sweeper.run()
num_points_result = len(result["vector"])
print(f"Measured at {num_points_result} frequency points.")
sweeper.plot()
5. Verify the spectroscopy pulse with the Scope

To achieve the best SNR, integration should start when the pulse reaches the integration units. Here, the scope is used to verify the spectroscopy pulse and measure the integration delay.

The Scope is configured in a single mode triggered by the Sequencer 1 Trigger Output without any trigger delay, the recorded channel is physical Input Channel 1, and the recording duration is 1.5 μs. The trigger generated from the Sequencer 1 is also used to trigger Spectroscopy measurement to send the spectroscopy pulse. The pulse recorded by the Scope is shown in Figure 2. The measured phase of the input signal depends on the delay between signal generation at the Readout Pulse Generator and signal arrival at the Integration Units. The integration delay can be simply obtained by checking the delay of the pulse from the Scope. In the loopback mode the delay is about 220 ns.

SCOPE_CHANNEL = 0
RECORD_DURATION = 1.5e-6

with device.set_transaction():
device.scopes[0].trigger.enable(1)
device.scopes[0].trigger.channel(32 + CHANNEL_INDEX) # Sequencer 1 Trigger Output
device.scopes[0].trigger.delay(0) # in units of second
device.scopes[0].length(int(RECORD_DURATION * SAMPLING_FREQUENCY/16)*16)
device.scopes[0].channels[SCOPE_CHANNEL].inputselect(CHANNEL_INDEX) # physical Channel 1
device.scopes[0].channels[SCOPE_CHANNEL].enable(1)

device.qachannels[CHANNEL_INDEX].oscs[0].freq(0) # set oscillator frequency to 0 Hz
device.qachannels[CHANNEL_INDEX].spectroscopy.trigger.channel(32 + CHANNEL_INDEX) # Sequencer 1 Trigger Output

seqc_program = """\
repeat(1) {
resetOscPhase(); // reset the digital oscillator phase of Channel 1
setTrigger(1); setTrigger(0); // send out Sequencer 1 Trigger
}
"""
device.scopes[0].run(single = True) # run the scope
scope_data, *_ = device.scopes[0].read() # readout the data from the Scope