Skip to content

Appendix B: Answers

3. Analog Input Calibration

  1. Consider an uncalibrate time of flight (24 ns) and remove the wait() command, How would the recorded raw ADC trace would look like?
    • The recording window will show the tail end of the previous pulse. In the displayed figure, the oscillating signal appears to fill the entire time axis, even though the time of flight hasn't been calibrated.
  2. What is the problem if the recorded raw ADC amplitude is below 1 milivolt?
    • Refer to the hardware specifications, and you'll see that the ADCs offer a 12-bit resolution for a range of +/- 0.5 Volts. This means the smallest detectable change (digitization granularity) is approximately 0.25 mV. If your input signal is near or below 1 mV, it could introduce noise and inaccuracies to the signal you capture. Therefore, it's recommended to amplify the signal!
  3. 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?
    • Analog input offsets are applied after the signal has been digitized. This leads to two key points:
      1. Tweaking the analog input offset won't recover the dynamic range at the input if there's an undesired DC offset.
      2. If you record a 0.4 V signal and then introduce a DC offset, the signal will be capped at 0.5 V. You can test this out!
  4. Calculate the data size in megabytes transferred to your computer when you execute measure() and the length of the pulse is 5 microseconds and take 1000 shots, as mentioned on line 26 of raw_adc_traces.py.
    • In the QM platform, each data point is represented by 32 bits. If you record an ADC trace for 5 microseconds, it will be 5x32 bits in size. Taking 1,000 shots will still result in an average signal size of 5x32 bits. However, collecting all 1,000 of these traces will result in a total size of 5x32x1,000 bits. Consequently, when transferring data from the OPX to your computer, you might be sending a total of approximately 0.0191 megabytes.

4. Spectroscopy

  1. Without using update_frequency(), create a spectroscopy sweep for the following frequencies [2, 3, 6, 8, 12] MHz. Verify that you succesfully created the five different pulses either using simulated_samples or an oscilloscope.

    • To conduct spectroscopy without the update_frequency() function in QUA:

      1. Create five distinct operations named readout_2MHz through readout_12MHz.
      2. Ensure the intermediate_frequency of the resonator element is set to zero.
      3. For each of the aforementioned operations, associate dedicated pulses with specific waveforms. These waveforms should define the frequencies point by point, such as 2, 3, 6, 8, and 12 MHz, rather than just being envelopes multiplied by the oscillator in the PPU.
      4. It's important to note that the waveform list processes at 1 GSa/s.

    Using the OPX like an AWG is quite labor-intensive compared to using it as a dedicated quantum experiment processor.

  2. Modify the real-time dual demodulation process to exclusively utilize demod.full() and still achieve identical result as I and Q.

    • Note that dual_demod does cosxout1 + sinxout2 = I, so to use demod.full you will need to write four statements,
      1. demod.full('cos', II, 'out1')
      2. demod.full('sin', IQ, 'out2')
      3. demod.full('minus_sin', QQ, 'out1')
      4. demod.full('cos', QI, 'out2')

    You can either retrive them seprately into dedicated streams or perform the arithmetic in QUA and retrieve the data as in the form of dual demodulation, I and Q.

  3. Adjust the QUA program to generate the qubit and the resonator pulse to coincide in time as well as to have the same duration.

    • The compiler assigns separate threads to both qubit and resonator, allowing them to operate concurrently. We ensured sequential execution using align(). To modify this:

      1. First, remove the align() function.
      2. Then, adjust the durations of saturation and readout in configuration.py to ensure they are of equal length.
  4. Why is it beneficial to place the pause() function in the outer loop?

    • Everytime the pause() function is called we active instructions in your computer to make changes to instruments and also to call resume() which is an operation that transfer information via the LAN to resume the PPU's work. If we place it in the outer loop we minimize the time we communicate between the computer and the OPX, thus creating minimal communication overhead.
  5. How much additional time would the QUA program require if the pause() function were moved from the outer loop to the inner most loop?

    • Assume that each pause() and resume() cycle takes 1 second. Quantify the number of iterations and calculate the time it takes in the outer loop and in the inner-most loop.
  6. Considering that the qubit and resonator operate in separate threads having the possibility of generatingn paralellism, and needing align() to make them sequential. How come at each for-loop iteration the qubit pulse always happens after the resonator pulse.

    • Well it turns out that with qua.for_() has an implicit align() not displayed and is under the QUA, we implemented it because at the beginning of QUA many users forgot to align() the threads and would have pulses from different iterations being combined thus producing menaingless results.

5. Power-Time Rabi

  1. Reconfigure the Rabi sequence from lines 32 to 35, place the amplitude sweep as the outer loop, the duration as the middle, and the shots as the innermost loop; then, how should the SPU pipelines be set up to retrieve averaged 2D arrays?

    • The stream processing would look like this
        with qua.stream_processing():
            I_st.buffer(n_avg).map(FUNCTIONS.average()).buffer(len(amplitudes)).buffer(len(durations)).save("I")
            Q_st.buffer(n_avg).map(FUNCTIONS.average()).buffer(len(amplitudes)).buffer(len(durations)).save("Q") 
    
  2. Modify the for-loop to: with qua.for_(a, 0, a < a_max, a): (line 34); note that we disabled the update condition. To implement the iterations once again, which QUA code line would you add to the indented instructions? Implement it.

    • After line 34, insert the code assign(a, a + da). You can obtain the delta value from line 20. Before entering the amplitude for-loop that follows line 33, reset the value of a with the code assign(a, 0.0).
  3. Generate a simplified program to test the following. Instead of using assign(a, a + da), use the Python command a += da, and then fetch the results using save(a, a_st) to your computer.

    • To fetch the results you need to create a_st = qua.declare_stream(), and also add another line of code to the SPU, a_st.save_all("a_values"), and then fetch by a_values = job.result_handles.get("a_values").fetch_all()['value'].
  4. Instead of using a QUA for-loop for generating 100k shots in the outer averaging loop (line 30), use a Python loop -> for n in range(100_000).

    • Because of the Python for loop, you will exhaust the program memory if you write 100,000 lines of QUA code, since each line typically consumes 1 instruction unit of memory.
  5. What occurs if you have a=7.8 and do assign(a, a+1)? Similarly, what happens with a=-7.8 when you apply assign(a, a-1)?

    • Fixed QUA variables value between [-8, 8> and if you go beyond this boundaries you overflow.
  6. What occurs if you have n=2_147_473_648 and do assign(n, n+10_000)? Similarly, what happens with n=-2_147_473_648 when you do assign(n, n-10_000)?

    • Integer QUA variables range between [-2^31, 2^31-1> and if you go beyond this boundaries your overflow.
  7. Consider using flat-top Gaussian pulse instead of a standard Gaussian pulse. In a time-Rabi sequence, if you wish to scan only the flat-top section while maintaining a constant rise and fall, how would you use duration to achieve it?

    • You will need to create three operations gaussian_rise, gaussian_flatop, and gaussian_fall and place them in the elements dictionary as part of qubit. Then you need to do the following codeblock,
        play('gaussian_rise', 'qubit')
        play('gaussian_flatop', 'qubit', duration=t)
        play('gaussian_fall', 'qubit')
    
  8. If the Gaussian pulse's amplitude is to 0.17 V in the configuration dictionary, and during a power-Rabi you sweep the a parameter up to 1.5, what is the highest voltage measured at the analog outputs?

    • It will be 0.17x1.5 = 0.255 V.
  9. What would occur if you set the Gaussian pulse amplitude to 0.4 V, and sweep the a parameter up to 1.5?

    • The signal will clip at the DACs output at 0.5 V.
  10. Why do you need to multiply amplitudes * x180_amp and 4 * durations when plotting your results in line 109 of the QUA program?

    • It is because amplitudes is only the scaling prefactor and durations used in the QUA program needs to be in clock cycles. To visualize the results in Volts and nanoseconds, you need the mentioned multiplications.

6. Chevron-Ramsey

  1. The function frame_rotation_2pi(Cast.mul_fixed_by_int(Cast.to_fixed(f * 1e-9), 4 * delay),"qubit") has three inner operations: f * 1e-9, 4 * delay, and Cast.mul_fixed_by_int(). Perform each operation individually, and save the results into QUA variables and pass them to a data stream. Retrieve the results onto your computer and verify that the PPU executed contrasting them with Python calculations.

    • Use the following code block,

          assign(det, Cast.to_fixed(f * 1e-9))
          assign(del_t, 4 * delay)
          assign(phase, Cast.mul_fixed_by_int(det, del_t))
          save(det, det_st)
          save(del_t, del_t_st)
          save(phase, phase_st)
      
          with qua.stream_processing():
              det_st.save_all("det")
              del_t_st.save_all("del_t_st")
              phase_st.save_all("phase")
      
  2. Implement the frame_rotation_2pi() function using the amplitude matrix and employ real-time mathematical operations to calculate both cosine and sine.

    • Use play('x90'*amp(a_11, a_12, a_21, a_22), 'qubit'), and assign(a_11, Math.cos(angle)), assign(a_12, Math.sin(-angle)), assign(a_21, Math.sin(angle)), and assign(a_22, Math.cos(angle)).
  3. How can you utilize real-time mathematics together with frame_rotation() to replicate the functionality of frame_rotation_2pi()?

    • If a phase value might surpass the set precision limits, you should develop a method to handle numbers greater than these limits. This involves using int and binary operations. Additionally, you'll need to implement the modulo operation since it isn't natively supported in QUA.
  4. How would include delay = 0 into the QUA program to create play('x90', 'qubit') - wait(0) - play('x90', 'qubit')? Do not use QUA if-statement.

    • Generate an outside loop of the following form, and adjust the stream processing to take one extra sample,
    with progam() as prog:
    
        with qua.for_(n, 0, n < n_avg, n + 1):
            with qua.for_(*from_array(f, freq_array)):
    
                play("x90", "qubit")
                play("x90", "qubit")
                qua.align("qubit", "resonator")
                measure(
                    "readout",
                    "resonator",
                    None,
                    qua.dual_demod.full("cos", "sin", I),
                    qua.dual_demod.full("minus_sin", "cos", Q),
                )
                qua.wait(thermalization_time // 4, "resonator")
                save(I, I_st)
                save(Q, Q_st)
    
                with qua.for_(*from_array(delay, taus)): 
    
                ...
    
        with qua.stream_processing():
            I_st.buffer(len(taus)+1).buffer(len(freq_array)).average().save("I")
            Q_st.buffer(len(taus)+1).buffer(len(freq_array)).average().save("Q")
            n_st.save("iteration")
    
  5. What would the result be if you were to remove reset_frame() in line 60 of the QUA program?

    • The phase continuously accumulates, and when averaged over time in the SPU, any irregularities in the data's coherent oscillations might be masked or smoothed out.

7. Active Reset

  1. Quantify the Real-Time Feedback latency for the two cases of using if-statements and conditional-play.

    • if-statement latency: 245 ns, and conditional play latency: 200 ns. You will need have a recording window that can capture the pulse that readout pulse and the conditional played pulse at the analog inputs. Thus, you can create a third element that has a longer readout length to capture a longer raw ADC trace.
  2. Modify the original QUA code to implement Repeat Until Success for Active Reset.

    • Use with while_(),
    with qua.program() as prog:
    
        with while_(I > ge_threshold): # repeat until success
            measure(
                    "readout",
                    "resonator",
                    None,
                    dual_demod.full("rotated_cos", "out1", 
                                    "rotated_sin", "out2", I),
                    dual_demod.full("rotated_minus_sin", "out1", 
                                    "rotated_cos", "out2", Q),
            )
    
            with if_(I > ge_threshold):
                play("x180", "qubit")
    
            measure(
                    "readout",
                    "resonator",
                    None,
                    dual_demod.full("rotated_cos", "out1", 
                                    "rotated_sin", "out2", I),
                    dual_demod.full("rotated_minus_sin", "out1", 
                                    "rotated_cos", "out2", Q),
            )
    
  3. Given the distribution of the two states provided below, formulate various Active Reset methods,

    • To maximize the ground state preparation fidelity implement repeat-until-success and make the condition to exit with while_() for the I value to be a few standar deviations deep into the ground state distribution.
    • To make a compromise between the preparation fidelity and run-time, define the exit condition in with while_() to be the mean value of the ground state distribution, and conditionally play() in the threshold between the excited and ground state.

    Start to think about how to implement Bayesian estimation to dynamically estimate the threshold for better preparation fidelity.

8 .Randomized Benchmarking

  1. For a depth of 3, and the random number in each iteration to be 7.

    • depth = 1, circuit = -x90-y90, inv_gate = y90x90
    • depth = 2, circuit = -x90-y90-x90-y90, inv_gate = -x90-y90
    • depth = 3, circuit = -x90-y90-x90-y90-x90-y90, inv_gate = I
  2. For a depth of 13, and the random number list for the sequential iterations to be [3, 17, 22, 8, 0, 14, 19, 6, 21, 11, 15, 5, 20]

    • depth = 1, circuit = 3, inv_gate = 3 (switch/case values)
    • depth = 2, circuit = 3-16, inv_gate = 17
    • depth = 3, circuit = 3-16-1, inv_gate = 1
    • depth = 4, circuit = 3-16-1-11, inv_gate = 4
    • depth = 5, circuit = 3-16-1-11-11, inv_gate = 4
    • depth = 6, circuit = 3-16-1-11-11-16, inv_gate = 17
    • depth = 7, circuit = 3-16-1-11-11-16-9, inv_gate = 5
    • depth = 8, circuit = 3-16-1-11-11-16-9-1, inv_gate = 1
    • depth = 9, circuit = 3-16-1-11-11-16-9-1-20, inv_gate = 20
    • depth = 10, circuit = 3-16-1-11-11-16-9-1-20-23, inv_gate = 23
    • depth = 11, circuit = 3-16-1-11-11-16-9-1-20-23-9, inv_gate = 5
    • depth = 12, circuit = 3-16-1-11-11-16-9-1-20-23-9-0, inv_gate = 0
    • depth = 13, circuit = 3-16-1-11-11-16-9-1-20-23-9-0-20, inv_gate = 20
  3. Given that we declared the switch/case to be unsafe=True, What would occur if you remove with case_(5) statement in the play_sequence() macro?