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