Sweeper Module for Pulsed spectroscopy

In this tutorial, the SHFQA is used to perform pulsed spectroscopy measurement with customized waveform envelope using Python API Sweeper module. The measurement starts with verifying the readout pulses envelop, is followed by spectroscopy delay calibration, and is finished with the pulsed spectroscopy measurement.

Goals and Requirements

This tutorial will show users how to

  • configure the input and output parameters, pulse envelop, sweep and average parameters, and trigger of the SHFQA Sweeper,

  • verify the spectroscopy pulse using the SHFQA Monitor Scope,

  • calibrate the delay between when the readout pulse is generated and when it arrives at the Monitor Scope,

  • run the Sweeper with a customized envelope and obtain the measurement data.

This tutorial is applicable to all SHFQA Instruments and no additional instrumentation is needed.

Users can download all LabOne API Python example files introduced in this tutorial from GitHub, https://github.com/zhinst/labone-api-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 ziPython, LabOne and the Firmware of the SHFQA device are updated and compatible,

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

  • start LabOne and open the LabOne graphical user interface using the default web browser,

  • prepare an empty python script,

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

Tutorial

Users can use the Python code below to perform pulsed spectroscopy measurement, and each step is explained as the following.

  1. Connect the SHFQA to a host computer

    Open a daq-session to the dev-instrument using this code from the tutorial "Connecting to the instrument" and replace devXXXXX with the ID of the SHFQA instrument, e.g. dev12024).

  2. Import the Sweeper and other packages

    Import the Sweeper classes to configure the Sweeper. Import utility package, helper package, and basic calculation and plotting package to assist this tutorial.

    import numpy as np
    import matplotlib.pyplot as plt
    import zhinst.utils
    from zhinst.utils.shf_sweeper import (
        ShfSweeper,
        AvgConfig,
        RfConfig,
        SweepConfig,
        TriggerConfig,
        EnvelopeConfig,
    )
    import shf_utils
    import helper_resonator
    import helper_com
  3. Configure the SHFQA Sweeper

    The Sweeper module is issued by the daq-session and the device ID dev. The configuration of the Sweeper module includes sweep parameters configuration set by sweep_config, average configuration set by avg_config, RF parameter configuration set by rf_config, trigger configuration set by trig_config, and the envelope configuration set by envelop_config. All parameters are uploaded to the instrument by sweeper.set_to_device.

    The readout pulse envelop is defined by a flap-top Gaussian function with pulse duration of 1 μs, rise and fall time of 0.05 μs, modulation frequency of 0 Hz, and scaling factor of 1. The envelope_delay, i.e., the delay between the trigger and propagation of the output signal, is set to 0.

    The input and output parameters are defined such that the readout signal is sent out from channel 1 with a center frequency of 4 GHz, and output and input power range of 0 dBm.

    The sweep parameters are defined such that the offset frequency is linearly swept over 51 points from -200 MHz to 300 MHz with an oscillator gain of 0.8. Users can also sweep the frequency logarithmically with "log" mapping. The integration length is the same as the pulse duration, and the results are averaged after the measurement is repeated twice at each frequency step, i.e., using sequential averaging mode. With another averaging mode called "cyclic", the results are averaged cyclically.

    The Sweeper is triggered by "channel0_trigger_input0", i.e., channel 1 trigger A in the front panel. The trigger level is set to 0 V. Users can either choose a real external trigger source, or the "loopback" option in which marker output and trigger input of the same channel are internally connected. "loopback" trigger mode is used in this tutorial.

    # instantiate ShfSweeper
    sweeper = ShfSweeper(daq, dev)
    
    # generate complex pulse envelope with a zero modulation frequency
    envelope_duration = 1.0e-6
    envelope_rise_fall_time = 0.05e-6
    envelope_frequencies = [0]
    envelope_delay = 0
    flat_top_gaussians = helper_commons.generate_flat_top_gaussian(
        envelope_frequencies,
        envelope_duration,
        envelope_rise_fall_time,
        shf_utils.Shfqa.SAMPLING_FREQUENCY,
        scaling=1,
    )
    flat_top_gaussians_key = 0
    pulse_envelope = flat_top_gaussians[flat_top_gaussians_key]
    envelope_config = EnvelopeConfig(waveform=pulse_envelope, delay=envelope_delay)
    
    # configure sweeper
    rf_config = RfConfig(channel=0, input_range=0, output_range=0, center_freq=4e9)
    sweep_config = SweepConfig(
        start_freq=-200e6,
        stop_freq=300e6,
        num_points=51,
        mapping="linear",
        oscillator_gain=0.8,
    )
    avg_config = AvgConfig(
        integration_time=envelope_duration,
        num_averages=2,
        mode="sequential",
        integration_delay=spectroscopy_delay,
    )
    
    # use the marker output via loopback to trigger the measurement
    # remove this code when using a real external trigger (e.g. an HDAWG)
    # force the trigger channel
    trigger_source = "channel0_trigger_input0"
    # enable the loopback trigger
    helper_resonator.set_trigger_loopback(daq, dev)
    trig_config = TriggerConfig(source=trigger_source, level=0)
    
    sweeper.configure(sweep_config, avg_config, rf_config, trig_config, envelope_config)
    
    # set to device, can also be ignored but is needed to verify envelope before sweep
    sweeper.set_to_device()
  4. Validate the spectroscopy pulse with the Monitor Scope

    The Monitor Scope is used to record the spectroscopy pulse with the offset frequency of 0 Hz. By turn on both signal input and output of channel 1, and synchronize the daq, the instrument is ready to sending out pulses and acquiring data. The Monitor Scope is configured such that the input of channel 1 is monitored, it is triggered by the same trigger used for the sweeper, and the acquired trace length is the same as the envelope duration. With the helper function, the acquired data is plotted, as shown in Figure 1.

    # turn on the input / output channel
    daq.setInt(f"/{dev}/qachannels/{rf_config.channel}/input/on", 1)
    daq.setInt(f"/{dev}/qachannels/{rf_config.channel}/output/on", 1)
    daq.sync()
    
    
    daq.setDouble(f"/{device_id}/qachannels/{rf_config.channel}/oscs/0/freq", 0)
    scope_trace = helper.measure_resonator_pulse_with_scope(
        daq,
        dev,
        channel=rf_config.channel,
        trigger_input=trig_config.source,
        pulse_length_seconds=envelope_duration,
    )
    
    helper.plot_resonator_pulse_scope_trace(scope_trace)
  5. Calibrate the spectroscopy delay

    To achieve the best SNR, the readout pulse coming from the device under test should fully overlap with the demodulation pulse. Therefore, the delay, i.e., how much time it takes for the readout pulse to propagate through the instrument and all the cables to the integration unit, has to be calibrated. This can be done by comparing the data recorded on the scope with the original data. The calibrated delay is 220 ns and this parameter will be used for the pulsed spectroscopy measurement.

    # simple filter window size for filtering the scope data
    window_s = 4
    
    # apply the filter to both the intial pulse and the one observed with the scope
    # to introduce the same amount of 'delay'
    pulse_smooth = np.convolve(
        np.abs(pulse_envelope), np.ones(window_s) / window_s, mode="same"
    )
    pulse_diff = np.diff(np.abs(pulse_smooth))
    sync_tick = np.argmax(pulse_diff)
    
    scope_smooth = np.diff(np.abs(scope_trace))
    scope_diff = np.convolve(
        scope_smooth, np.ones(window_s) / window_s, mode="same"
    )
    sync_tack = np.argmax(scope_diff)
    
    delay_in_ns = (
        1.0e9 * (sync_tack - sync_tick) / shf_utils.Shfqa.SAMPLING_FREQUENCY
    )
    delay_in_ns = 2 * ((delay_in_ns + 1) // 2)  # round up to the 2ns resolution
    print(f"delay between generator and monitor: {delay_in_ns} ns")
    print(f"Envelope delay: {envelope_delay * 1e9:.0f} ns")
    if spectroscopy_delay * 1e9 == delay_in_ns + (envelope_delay * 1e9):
        print("Spectroscopy delay and envelope perfectly timed!")
    else:
        print(
            f"Consider setting the spectroscopy delay to [{(envelope_delay + (delay_in_ns * 1e-9))}] "
        )
        print("to exactly integrate the envelope.")
  6. Start the pulsed spectroscopy measurement and plot the results

    The measurement starts after running sweeper.run, and the results is acquired and plotted, as shown in Figure 2. The calculation from voltage to the power and phase can be found in the tutorial of Sweeper Module for Spectroscopy.

    # start a sweep
    result = sweeper.run()
    print("Keys in the ShfSweeper result dictionary: ")
    print(result.keys())
    
    # alternatively, get result after sweep
    result = sweeper.get_result()
    num_points_result = len(result["vector"])
    print(f"Measured at {num_points_result} frequency points.")
    
    # simple plot over frequency
    weeper.plot()
    
    helper_resonator.clear_trigger_loopback(daq, dev)
    pulsed spectroscopy
    Figure 1. Readout pulse envelop in spectroscopy mode
pulsed spec power phase
Figure 2. Power and phase of readout results versus offset frequency.