To calibrate your system's analog inputs, use raw_adc_traces.py. It contains clear annotations with valuable information to improve our understanding of the QM platform.
The measure("readout", "resonator", ...) command digitizes signals from analog inputs. The digitized signal can go to the PPU for real-time calculations or directly to the SPU. For raw ADC traces, measure("readout", "resonator", adc_st) sends the signal straight to the SPU.
# Go to line 23importqm.quaimportquafromqmimportSimulationConfig,LoopbackInterfacefromqmimportQuantumMachinesManagerfromOPX1000configurationimportconfig,qop_ip,qop_port,cluster_name,octave_config# or OPXplusconfiguration, depending on your hardware importmatplotlib.pyplotaspltimportnumpyasnp############################### Program-specific variables ###############################n_avg=2#################### The QUA program ####################withqua.program()asraw_trace_prog:n=qua.declare(int)adc_st=qua.declare_stream(adc_trace=True)withqua.for_(n,0,n<n_avg,n+1):qua.reset_phase("resonator")qua.measure("readout","resonator",adc_st)# (1)!qua.wait(1_000,"resonator")# time input is in clock cycles (4 ns)withqua.stream_processing():# Will save average:adc_st.input1().average().save("adc")# Will save only last run:adc_st.input1().save("adc_single_run")###################################### Open Communication with the QOP ######################################simulate=Truesimulation_in_cloud=Trueifsimulate:ifsimulation_in_cloud:client=QmSaas(email=cloud_username,password=cloud_password)instance=client.simulator(QoPVersion.v3_2_0)instance.spawn()qmm=QuantumMachinesManager(host=instance.host,port=instance.port,connection_headers=instance.default_connection_headers)else:qmm=QuantumMachinesManager(host=qop_ip,cluster_name=cluster_name)simulation_config=SimulationConfig(duration=1_000)# duration is in units of clock cycles, i.e., 4 nanosecondsjob=qmm.simulate(config,raw_trace_prog,simulation_config)job.get_simulated_samples().con1.plot()ifsimulation_in_cloud:instance.close()plt.show()else:qm=qmm.open_qm(config)job=qm.execute(raw_trace_prog)# Creates a result handle to fetch data from the OPXres_handles=job.result_handles# Waits (blocks the Python console) until all results have been acquiredres_handles.wait_for_all_values()# Fetch the raw ADC traces and convert them into Voltsadc=u.raw2volts(res_handles.get("adc").fetch_all())adc_single_run=u.raw2volts(res_handles.get("adc_single_run").fetch_all())# Plot dataplt.figure()plt.subplot(121)plt.title("Single run")plt.plot(adc_single_run.real,label="I")plt.plot(adc_single_run.imag,label="Q")plt.xlabel("Time [ns]")plt.ylabel("Signal amplitude [V]")plt.legend()plt.subplot(122)plt.title("Averaged run")plt.plot(adc.real,label="I")plt.plot(adc.imag,label="Q")plt.xlabel("Time [ns]")plt.legend()plt.tight_layout()plt.show()
The QUA instruction measure() once compiled, it's converted into PPU instructions for signal digitization and further data manipulation. "readout" and "resonator" are the operation and element, respectively. The third argument, adc_st instructs the PPU to digitize the signal and forward it to the Stream Processing Unit (SPU).
The SPU is adjacent to the PPU. They operate concurrently: the SPU begins processing when the PPU sends data, while the PPU continues the QUA program. In this example, each with qua.for_(n, 0, n < n_avg, n+1) iteration passes the digitized signal to the SPU for processing. Meanwhile, the PPU starts the next iteration in parallel.
# Go to lines 19, and 26-30importqm.quaimportquafromqmimportSimulationConfig,LoopbackInterfacefromqmimportQuantumMachinesManagerfromOPX1000configurationimportconfig,qop_ip,qop_port,cluster_name,octave_config# or OPXplusconfiguration, depending on your hardwareimportmatplotlib.pyplotaspltimportnumpyasnp############################### Program-specific variables ###############################n_avg=2#################### The QUA program ####################withqua.program()asraw_trace_prog:n=qua.declare(int)adc_st=qua.declare_stream(adc_trace=True)# (1)!withqua.for_(n,0,n<n_avg,n+1):qua.reset_phase("resonator")qua.measure("readout","resonator",adc_st)qua.wait(1_000,"resonator")# time input is in clock cycles (4 ns)withqua.stream_processing():# (2)!# Will save average:adc_st.input1().average().save("adc")# Will save only last run:adc_st.input1().save("adc_single_run")###################################### Open Communication with the QOP ######################################simulate=Truesimulation_in_cloud=Trueifsimulate:ifsimulation_in_cloud:client=QmSaas(email=cloud_username,password=cloud_password)instance=client.simulator(QoPVersion.v3_2_0)instance.spawn()qmm=QuantumMachinesManager(host=instance.host,port=instance.port,connection_headers=instance.default_connection_headers)else:qmm=QuantumMachinesManager(host=qop_ip,cluster_name=cluster_name)simulation_config=SimulationConfig(duration=1_000)# duration is in units of clock cycles, i.e., 4 nanosecondsjob=qmm.simulate(config,raw_trace_prog,simulation_config)job.get_simulated_samples().con1.plot()ifsimulation_in_cloud:instance.close()plt.show()else:qm=qmm.open_qm(config)job=qm.execute(raw_trace_prog)# Creates a result handle to fetch data from the OPXres_handles=job.result_handles# Waits (blocks the Python console) until all results have been acquiredres_handles.wait_for_all_values()# Fetch the raw ADC traces and convert them into Voltsadc=u.raw2volts(res_handles.get("adc").fetch_all())adc_single_run=u.raw2volts(res_handles.get("adc_single_run").fetch_all())# Plot dataplt.figure()plt.subplot(121)plt.title("Single run")plt.plot(adc_single_run.real,label="I")plt.plot(adc_single_run.imag,label="Q")plt.xlabel("Time [ns]")plt.ylabel("Signal amplitude [V]")plt.legend()plt.subplot(122)plt.title("Averaged run")plt.plot(adc.real,label="I")plt.plot(adc.imag,label="Q")plt.xlabel("Time [ns]")plt.legend()plt.tight_layout()plt.show()
The adc_st = declare_stream(adc_trace=True) code line initiates a data stream, representing the path data takes from the analog input to the SPU. By setting adc_trace=True, we instruct the SPU to process the data in batches of the size of the measurement time. For example, the adc_st of a signal that is digitized for 500 nanoseconds will have a data size of 500 points.
Every code line indented under with qua.stream_processing(): provides instructions for the SPU. While the PPU and SPU operate concurrently, it's crucial to differentiate the SPU instructions from the PPU ones, seen in lines 20 to 23. In the highlighted lines below, you're examining the stream processing of raw ADC traces. For an in-depth understanding of streaming raw ADC traces, please refer to our documentation.
The SPU can save processed data to disk while it still have active processing pipelines. Once the data is stored, your computer can retrieve it over the network.
# Go to lines 62-69fromqm.quaimport*fromqmimportSimulationConfig,LoopbackInterfacefromqmimportQuantumMachinesManagerfromOPX1000configurationimportconfig,qop_ip,qop_port,cluster_name,octave_config# or OPXplusconfiguration, depending on your hardwareimportmatplotlib.pyplotaspltimportnumpyasnp############################### Program-specific variables ###############################n_avg=2#################### The QUA program ####################withqua.program()asraw_trace_prog:n=qua.declare(int)adc_st=qua.declare_stream(adc_trace=True)withqua.for_(n,0,n<n_avg,n+1):qua.reset_phase("resonator")qua.measure("readout","resonator",adc_st)qua.wait(1_000,"resonator")withqua.stream_processing():adc_st.input1().average().save("adc1")adc_st.input2().average().save("adc2")adc_st.input1().save("adc1_single_run")adc_st.input2().save("adc2_single_run")###################################### Open Communication with the QOP ######################################simulate=Truesimulation_in_cloud=Trueifsimulate:ifsimulation_in_cloud:client=QmSaas(email=cloud_username,password=cloud_password)instance=client.simulator(QoPVersion.v3_2_0)instance.spawn()qmm=QuantumMachinesManager(host=instance.host,port=instance.port,connection_headers=instance.default_connection_headers)else:qmm=QuantumMachinesManager(host=qop_ip,cluster_name=cluster_name)simulation_config=SimulationConfig(duration=1_000)# duration is in units of clock cycles, i.e., 4 nanosecondsjob=qmm.simulate(config,raw_trace_prog,simulation_config)job.get_simulated_samples().con1.plot()ifsimulation_in_cloud:instance.close()plt.show()else:qm=qmm.open_qm(config)job=qm.execute(raw_trace_prog)res_handles=job.result_handles# (1)!res_handles.wait_for_all_values()# (2)!adc=u.raw2volts(res_handles.get("adc").fetch_all())# (3)!adc_single_run=u.raw2volts(res_handles.get("adc_single_run").fetch_all())# Plot dataplt.figure()plt.subplot(121)plt.title("Single run")plt.plot(adc_single_run.real,label="I")plt.plot(adc_single_run.imag,label="Q")plt.xlabel("Time [ns]")plt.ylabel("Signal amplitude [V]")plt.legend()plt.subplot(122)plt.title("Averaged run")plt.plot(adc.real,label="I")plt.plot(adc.imag,label="Q")plt.xlabel("Time [ns]")plt.legend()plt.tight_layout()plt.show()
This code line uses a command from the OPX+ job API or OPX1000 job API to access data stored on the SPU's hard disk.
With the QM platform, you can retrieve data from the SPU's hard disk immediately after it's saved, even as the SPU processes different pipelines. The command res_handles.wait_for_all_values() continuously checks if the processing has completed. If done, the Python console proceeds; if not, it waits.
Use res_handle.get("adc1") to access a specific result handle. The string inside the parentheses corresponds to the names in the SPU's save() statements. To transfer the data to your computer, use .fetch_all(). This places the data in the adc1 Python variable, but it's not yet saved on your computer's hard drive—this requires an additional step.
QUA-libs
You can find the full program in our qua-libs github repo in the following link.
Fetched data from simulation
During the simulation, if you encounter the error message Nothing to fetch: no results were found. Please wait until the results are ready in your terminal, it indicates that the SPU hasn't completed processing the pipelines, and no data is available. You can adjust the simulation duration or the parameters to obtain results.
qua.wait()
The qua.wait() command in QUA is used to deliberately insert pauses into your quantum experiment sequences. In the above program we use qua.wait() (line 24) to introduce a time where the resonator empties out before sending a pulse again.
Check the highlighted lines to see how raw_adc_traces.py links to the configuration dictionary. The outcome of analog input calibration is to update to calibrated values the time of flight.
config={"version":1,"controllers":{'con1':{"type":"opx1000","fems":{1:{# The keyword "band" refers to the following frequency bands:# 1: (50 MHz - 5.5 GHz)# 2: (4.5 GHz - 7.5 GHz)# 3: (6.5 GHz - 10.5 GHz)# Note that the "coupled" ports O1 & I1, O2 & O3, O4 & O5, O6 & O7, O8 & O1# must be in the same band, or in bands 1 & 3.# The keyword "full_scale_power_dbm" is the maximum power of# normalized pulse waveforms in [-1,1]. To convert to voltage,# power_mw = 10**(full_scale_power_dbm / 10)# max_voltage_amp = np.sqrt(2 * power_mw * 50 / 1000)# amp_in_volts = waveform * max_voltage_amp# ^ equivalent to OPX+ amp# Its range is -41dBm to +10dBm with 3dBm steps."type":"MW","analog_outputs":{1:{# (1)!"full_scale_power_dbm":-10,# units of dBm"band":2,"upconverter_frequency":7e9,# units of Hz},# resonator2:{"full_scale_power_dbm":0,"band":2,"upconverter_frequency":5e9},# qubit},"digital_outputs":{},"analog_inputs":{1:{"band":2,"downconverter_frequency":7e9},# (2)!},},2:{"type":"LF","analog_outputs":{# Flux line1:{"offset":0.0,# The "output_mode" can be used to tailor the max voltage and frequency bandwidth, i.e.,# "direct": 1Vpp (-0.5V to 0.5V), 750MHz bandwidth (default)# "amplified": 5Vpp (-2.5V to 2.5V), 330MHz bandwidth# Note, 'offset' takes absolute values, e.g., if in amplified mode and want to output 2.0 V, then set "offset": 2.0"output_mode":"direct",# The "sampling_rate" can be adjusted by using more FEM cores, i.e.,# 1 GS/s: uses one core per output (default)# 2 GS/s: uses two cores per output# NOTE: duration parameterization of arb. waveforms, sticky elements and chirping# aren't yet supported in 2 GS/s."sampling_rate":1e9,# At 1 GS/s, use the "upsampling_mode" to optimize output for# modulated pulses (optimized for modulated pulses): "mw" (default)# unmodulated pulses (optimized for clean step response): "pulse""upsampling_mode":"pulse",},},"digital_outputs":{1:{},},},},},},}
Here we declare the analog output 1 which we will link to the resonator in the respective elements dictionary. For in-depth technical specifications, refer to the documentation.
The resonator element measures data, so we must specify the analog inputs it's linked to.
Given that there is a delay of time between the signal outputs and its return to the analog intputs we use "time_of_flight" to adjust the recording window timing.
This code line shows two strings, the first one is the operation's name and the second one the name of the pulse associated with it.
The MWoutput dictionary contains the information on what analog input is associated with the resonator element.
The length field specifies both the duration of the ADC recording window and the microwave output duration, and the units are nanoseconds. In fact all time units in the configuration dictionary are in nanoseconds.
In this section, readout_wf and zero_wf are associated with I and Q, respectively. This might seem unconventional. However, the signal produced at the analog outputs results from multiplying these waveforms with matrices that contain phase, frequency, and amplitude details. For a deeper understanding, refer to the documentation.
The variable digital_marker associates a digital waveform to the readout operation, and in this specific example of saving raw ADC traces, a digital marker has to be associated with the operation if one wishes to save the raw ADC data, read the details in our documentation.
Using your preferred Python IDE, such as VScode, Jupyter, or Pycharm, save raw_adc_traces.py and reuse OPX1000configuration.py.
In the QUA program, change the value on line 41 to simulate = True.
Run the program in a Python console or within a notebook cell. The first image that appears simulates the timing of pulses from the analog outputs as they would be received by your experimental setup.
Next do the following changes and re-simulate,
Adjust the readout_amp to 10 millivolts in OPX1000configuration.py.
Set the readout_len to 1600 nanoseconds in OPX1000configuration.py.
Alter the signal's frequency to 2e6 Hz in OPX1000configuration.py.
Delete the qua.reset_phase() function in raw_adc_traces.py.
Keep in mind that the first three adjustments you made to the pulse – specifically its amplitude, duration, and frequency – are the key variables for achieving the highest readout fidelity with your qubit.
Updating the Configuration Dictionary After Experiments
The modifications you've made don't take effect in real-time; instead, they are implemented between experimental runs. For instance, when calibrating a qubit and a coupled-resonator device using different calibration codes, you'll adjust parameters like the frequencies of the qubit and resonator, as well as the amplitude and duration of the pulses, in between different calibration nodes.
To wrap up the Analog Input Calibration follow the next steps to update the Configuration dictionary, and look at the second figure that pops up,
Write down the time at which the signal starts and update time_of_flight by adding the number you just measured to the one already existing in the configuration.
To ensure high readout fidelity to correctly configure the time_of_flight for accurately recording the signal. These are prerequisites for achieving optimal qubit state discrimination.
Give it a go with qm.execute(), and to be able to visualize a signal connect a cable between analog output1 and analog input 1.
Consider an uncalibrate time of flight (24 ns) and remove the wait() command, How would the recorded raw ADC trace would look like?
What is the problem if the recorded raw ADC amplitude is below 1 milivolt?
If you set the analog input offset to 0.25 V, and send a signal of 0.4 V amplitude from the analog outputs to the inputs, how would the raw ADC trace look like?
Calculate the data size in megabytes transferred to your computer when you execute measure() and the length of the pulse is 5 microseconds and you take 1000 shots.
In the upcoming module, you'll experience the real-time capabilities of the QM platform firsthand. You'll be making frequency adjustments and processing waveforms in real-time, all demonstrated through a spectroscopy example.