Skip to content

Pulsed Resonator Spectroscopy


This tutorial is applicable to all SHFQA Instruments.

Goals and Requirements

The goal of this tutorial is to demonstrate how to use SHFQA to perform pulsed spectroscopy measurement with a customized pulse envelope using Zurich Instruments Toolkit API.

To run the tutorial, 1 SHFQA and loopback connection are required.


Users can download all zhinst-toolkit example files introduced in this tutorial from GitHub,


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 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 SHFQA

output (input) to the readout input (output) line, see SHFQA connection.

Figure 1: SHFQA connection.


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 import gaussian
    import numpy as np
    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
    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.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.mode("sequential") # "sequential" or "cyclic"
    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():
  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
    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

    Please note that default values of Sweeper parameters depend on the zhinst version, see tutorial Continuous Resonator Spectroscopy. Descriptions of other parameters can be found using the following code.

  4. Run the measurement and plot the data

    After executing, 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 =
    num_points_result = len(result["vector"])
    print(f"Measured at {num_points_result} frequency points.")
  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.

    RECORD_DURATION = 1.5e-6
    with device.set_transaction():
        device.scopes[0] + 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.qachannels[CHANNEL_INDEX].oscs[0].freq(0) # set oscillator frequency to 0 Hz
        device.qachannels[CHANNEL_INDEX] + 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
    device.qachannels[CHANNEL_INDEX].generator.load_sequencer_program(seqc_program) # load SeqC program to the Sequencer
    device.qachannels[CHANNEL_INDEX].generator.single(1) # using single mode
    device.qachannels[CHANNEL_INDEX].generator.enable(1) # run the Sequencer
    scope_data, *_ = device.scopes[0].read() # readout the data from the Scope

    Figure 2: Pulse envelope recorded by the Scope

    Figure 3: Pulsed spectroscopy measurement results in loopback configuration.