Skip to content

Integration Weights Measurement

Note

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

Goals and Requirements

The goal of this tutorial is to demonstrate how to use the SHFQC Scope to measure integration weights that are needed for high-fidelity single-shot readout using Zurich Instruments Toolkit API.

For qubit control with the SHFSG Signal Generator, the SHFQC Qubit Controller and the HDAWG Arbitrary Wave Generator, and instrument synchronization and feedback with the PQSC Programmable Quantum System Controller, see tutorials in the Online Documentation.

Note

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

In the tutorial, readout signals are acquired while the qubit is in ground and excited state. The readout pulse is generated by the SHFQC Readout Pulse Generator, and the signal acquisition is done by the SHFQC Scope. The integration weights are derived from the difference of the acquired readout signals.

  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, SHFQAChannelMode, Waveforms
    from scipy.signal.windows 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. Generate readout pulses

    Generate a flat-top Gaussian readout pulses with 8 offset frequencies, see Multiplexed Qubit Readout.

    NUM_QUBITS = 8
    SAMPLING_FREQUENCY = 2e9
    RISE_FALL_TIME = 10e-9
    PULSE_DURATION = 500e-9
    
    rise_fall_len = int(RISE_FALL_TIME * SAMPLING_FREQUENCY)
    pulse_len = int(PULSE_DURATION * SAMPLING_FREQUENCY)
    std_dev = rise_fall_len // 10
    
    gauss = gaussian(2 * rise_fall_len, std_dev)
    flat_top_gaussian = np.ones(pulse_len)
    flat_top_gaussian[0:rise_fall_len] = gauss[0:rise_fall_len]
    flat_top_gaussian[-rise_fall_len:] = gauss[-rise_fall_len:]
    # Scaling
    flat_top_gaussian *= 0.9
    
    readout_pulses = Waveforms()
    time_vec = np.linspace(0, PULSE_DURATION, pulse_len)
    
    for i, f in enumerate(np.linspace(2e6, 32e6, NUM_QUBITS)):
        readout_pulses.assign_waveform(
            slot=i,
            wave1=flat_top_gaussian * np.exp(2j * np.pi * f * time_vec)
        )
    
  3. Configure the channel

    Configure the channel to run in Readout mode, upload and send 100 readout pulses in which 50 of them to readout qubit in excited state and the rest for qubit in ground state, see Multiplexed Qubit Readout.

    # configure inputs and outputs
    CHANNEL_INDEX = 0 # physical Channel 1
    
    device.qachannels[CHANNEL_INDEX].configure_channel(
        center_frequency=5e9, # in units of Hz
        input_range=0, # in units of dBm
        output_range=-5, # in units of dBm
        mode=SHFQAChannelMode.READOUT, # SHFQAChannelMode.READOUT or SHFQAChannelMode.SPECTROSCOPY
    )
    device.qachannels[CHANNEL_INDEX].input.on(1)
    device.qachannels[CHANNEL_INDEX].output.on(1)
    
    # configure Generator
    device.qachannels[CHANNEL_INDEX].generator.write_to_waveform_memory(readout_pulses)
    device.qachannels[CHANNEL_INDEX].generator.configure_sequencer_triggering(
        aux_trigger="software_trigger0",# chanNtriginM, chanNseqtrigM, chanNrod
        play_pulse_delay=0, # 0s delay between startQA trigger and the readout pulse
    )
    
  4. Configure the Scope

    Configure the Scope to record 2 segments of data which are averaged 50 times. The trigger of the Scope is the Channel 1 Sequencer monitor Trigger which is enabled by the startQA command.

    SCOPE_CHANNEL = 0
    RECORD_DURATION = 600e-9 # in units of second
    NUM_SEGMENTS = 2
    NUM_AVERAGES = 50
    NUM_MEASUREMENTS = NUM_SEGMENTS * NUM_AVERAGES
    SAMPLING_FREQUENCY = 2e9 # in units of Hz
    
    device.scopes[0].configure(
        input_select={SCOPE_CHANNEL: f"channel{CHANNEL_INDEX}_signal_input"},
        num_samples=int(RECORD_DURATION * SAMPLING_FREQUENCY),
        trigger_input=f"channel{CHANNEL_INDEX}_sequencer_monitor0", # Sequencer 1 monitor trigger
        num_segments=NUM_SEGMENTS,
        num_averages=NUM_AVERAGES,
        trigger_delay=200e-9, # in units of second, record the data 200 ns later after receiving a trigger
    )
    
  5. Run the measurement and calculate the integration weights

    The integration weights for different qubits are measured sequentially with the for loop. In each loop, the Generator sends 100 pulses to readout 1 of the qubits prepared in ground and excited state, and the Scope acquires 2 segments of data which are averaged 50 times (see Figure 2), and the integration weights are calculated and plotted from the downloaded data (see Figure 3).

    results = []
    
    for i in range(NUM_QUBITS):
        qubit_result = {
            'weights': None,
            'ground_states': None,
            'excited_states': None
        }
        print(f"Measuring qubit {i}.")
    
        # upload sequencer program
        seqc_program = f"""
            repeat({NUM_MEASUREMENTS}) {{
                waitDigTrigger(1);
                startQA(QA_GEN_{i}, 0x0, true,  0, 0x0); // only QA_GEN_{i} matters for this measurement
            }}
        """
        device.qachannels[CHANNEL_INDEX].generator.load_sequencer_program(seqc_program)
    
        # Start a measurement
        device.scopes[SCOPE_CHANNEL].run(single=True)
        device.qachannels[CHANNEL_INDEX].generator.enable_sequencer(single=True)
        device.start_continuous_sw_trigger(
            num_triggers=NUM_MEASUREMENTS, wait_time=RECORD_DURATION
        )
    
        # get results to calculate weights and plot data
        scope_data, *_ = device.scopes[0].read()
    
        # Calculates the weights from scope measurements
        # for the excited and ground states
        split_data = np.split(scope_data[SCOPE_CHANNEL], 2)
        ground_state_data = split_data[0]
        excited_state_data = split_data[1]
        qubit_result['ground_state_data'] = ground_state_data
        qubit_result['excited_state_data'] = excited_state_data
        qubit_result['weights'] = np.conj(excited_state_data - ground_state_data)
        results.append(qubit_result)
    

    Figure 2: Readout pulses recorded by the Scope in loopback configuration

    Figure 3: Integration weights calculated from the readout pulses

Note

In order to achieve the highest possible resolution in the signal after integration, it’s advised to scale the dimensionless readout integration weights with a factor so that their maximum absolute value is equal to 1.