qilimanjaro-tech / qililab

Qililab is a generic and scalable quantum control library used for fast characterization and calibration of quantum chips. Qililab also offers the ability to execute high-level quantum algorithms with your quantum hardware.
Apache License 2.0
30 stars 2 forks source link

[BUG] Qprogram.sync() is not adding the wait instructions properly to the Q1ASM (Qblox) #670

Closed 4dri8 closed 7 months ago

4dri8 commented 8 months ago

Expected behavior

The modules should synchronize by using QProgram().sync(). Rabi taken from a well coordinated HW loop: image

Actual behavior

When trying to synchronize two modules, for example for a Rabi you need a QCM and a QRM, the QProgram().sync() is not adding the correct wait instructions, as the times it is adding are not synchronizing correctly the two modules, you can measure, but on every iteration of the average, the QRM is 16ns behind, this results in a noisy measurement.

As you can see below, the last timestamp written is just before the loop, and the QRM is 16ns later, this 16ns accumulate on every iteration, I thought about adding then 16ns of delay to the QCM, but by doing so the QProgram().sync() takes it into account and changes the waits again, so it stays behind.

QCM Q1ASM

setup:
                wait_sync        4                          # totals timestamp (instruction time)

main:
                move             1000, R0                   # 4ns (4ns)
avg_0:
                move             41, R1                     # 8ns (4ns)
                move             0, R2                      # 12ns (4ns)
loop_0:
                set_awg_gain     R2, R2                     # 20ns (8ns)
long_wait_10:
                move             3, R3                      # 24ns (4ns)
long_wait_10_loop:
                wait             65532                      # this loop is relax_time (200_000ns)
                loop             R3, @long_wait_10_loop     # loop takes 24ns when jump and 12ns when pass (84ns)
                wait             3404                       # 200_108ns 

                play             0, 1, 4                    # 200_112ns (4ns)
long_wait_12:
                wait             2040                       # 202_152ns (2_040ns)

                add              R2, 163, R2                # 202_164ns (add R,I,R 12ns)
                loop             R1, @loop_0                
                nop                             
                loop             R0, @avg_0     
                stop  

QRM Q1ASM

setup:
                wait_sync        4                          # totals timestamp (instruction time)

main:
                move             1000, R0                   # 4ns (4ns)
avg_0:
                move             0, R1                      # 8ns (4ns)
                move             0, R2                      # 12ns (4ns)
                move             0, R3                      # 16ns (4ns)
                move             41, R4                     # 20ns (4ns)
                move             0, R5                      # 24ns (4ns)
loop_0:
long_wait_9:
                move             3, R6                      # 28ns (4ns)
long_wait_9_loop:
                wait             65532                      # this loop is relax_time (200_000ns)
                loop             R6, @long_wait_9_loop      # loop takes 24ns when jump and 12ns when pass (84ns)
                wait             3404                       # 200_112ns

long_wait_11:
                wait             40                         # 200_152ns (40ns)

                play             0, 1, 4                    # 200_156ns (4ns)
                acquire_weighed  0, R3, R2, R1, 2000        # 202_156ns (2000ns)
                add              R3, 1, R3                  # 202_168ns (12ns)
                add              R5, 163, R5                # 202_180ns (12ns)
                loop             R4, @loop_0    
                loop             R0, @avg_0     
                stop  

Rabi taken with this program (Yeah, still a rabi, but a lot noisier, same parameters and same qubit as the first one): image

Additional information

There are other bugs to take into account when trying to reproduce this bug, in the play instruction on the QRM, there's extra wait time between play and acquire, this time should be the time of flight, but it is the waveform duration now, you can hardcode it to 200ns (more or less the tof) by now until it is solved.

Another bug to take into account is that the 2040ns wait of the QCM Q1ASM is coded to be the duration of play+acquire time by the QProgram().sync(), and as it takes the waveform duration as the wait for the playinstruction, it takes double the time it should. It should be just the duration of the readout (2000ns in this case).

Source code

#QProgram used

qp = ql.QProgram()

# Defining the variable Frequency for the driving pulse
amplitude = qp.variable(ql.Domain.Voltage)  # Just for the renormalization for the Q1ASM

with qp.average(HW_AVG):
    # Loop over all frequencies
    with qp.for_loop(variable=amplitude, start=AMP_VALUES[0], stop=AMP_VALUES[-1], step=AMP_VALUES[1]-AMP_VALUES[0]):
        qp.set_gain(bus=f"drive_q{QUBIT_IDX}_bus", gain=amplitude)
        qp.wait(bus=f"readout_bus_{QUBIT_IDX}",duration=REPETITION_DURATION)
        qp.sync()
        qp.play(bus=f"drive_q{QUBIT_IDX}_bus", waveform=drag)
        qp.wait(bus=f"readout_bus_{QUBIT_IDX}",duration=DRAG_DURATION)
        qp.play(bus=f"readout_bus_{QUBIT_IDX}", waveform=square_wf)  # Executing the Square pulse
        qp.acquire(bus=f"readout_bus_{QUBIT_IDX}", weights=ql.IQPair(I=weights_shape, Q=weights_shape))  # Measuring

Tracebacks

No response

System Information

Name: qililab
Version: 0.22.0
Summary: Fundamental package for fast characterization and calibration of quantum chips.
Home-page: https://github.com/qilimanjaro-tech/qililab
Author: Qilimanjaro Quantum Tech
Author-email: info@qilimanjaro.tech
License: Apache License 2.0
Location: /home/anavarro/miniconda3/envs/qililab-main/lib/python3.10/site-packages
Editable project location: /home/anavarro/qililab
Requires: h5py, lmfit, networkx, pandas, papermill, PyVISA-py, PyYAML, qblox-instruments, qcodes, qcodes-contrib-drivers, qibo, qiboconnection, qm-qua, qpysequence, qualang-tools, ruamel.yaml, rustworkx, submitit, tqdm, urllib3
Required-by: 

Platform info:             Linux-5.15.0-91-generic-x86_64-with-glibc2.35
Python version:            3.10.11
PyVISA version:            0.7.1
QCodes version:            0.42.0
QCodes Contrib version:    0.18.0
Qblox Instrument version:  0.11.2
Qpysequence version:       0.10.0
Quantum Machines version:  1.1.5.1
Qibo version:              0.1.12.dev0

Existing GitHub issues

linear[bot] commented 8 months ago

QHC-186 [BUG] Qprogram.sync() is not adding the wait instructions properly to the Q1ASM (Qblox)

rsagas commented 8 months ago

I am here interceding because I have been made aware of this problem.

WAIT SYNC (to trigger SYNQ) IS A NON-NEGOTIABLE MUST. Please lets have a meeting to ensure this is properly addressed.

fedonman commented 8 months ago

Hello @4dri8 and @rsagas. Thank you for raising this. This kind of detail in bug reporting is exactly what we want in order to be able to investigate more.

First of all, are you using main branch or the development branch of QProgram? Several issues that we found the last two weeks are resolved there.

In that branch, the play operation has an extra parameter wait_time to override the default wait time that is the duration of the pulse. So in order to measure with a time of flight you can do:

qp.play(bus=f"readout_bus_{QUBIT_IDX}", waveform=square_wf, wait_time=time_of_flight)  # Executing the Square pulse
qp.acquire(bus=f"readout_bus_{QUBIT_IDX}", weights=ql.IQPair(I=weights_shape, Q=weights_shape))  # Measuring

In general, using QProgram, all play instructions wait for the whole pulse, so there is no need to add an additional wait instruction afterwards. You are doing

qp.play(bus=f"drive_q{QUBIT_IDX}_bus", waveform=drag)
qp.wait(bus=f"readout_bus_{QUBIT_IDX}",duration=DRAG_DURATION)

and this adds DRAG_DURATION nanoseconds on top of the duration of the drag waveform.

Also, all buses are synchronized at the beginning of each loop iteration, so there is no point of having a sync() there. The sync() should be between the drive pulse and the readout pulse to enforce the second to begin right after the first.

My recommended way of writing the RABI would be:

time_of_flight = 120
qp = ql.QProgram()

# Defining the variable Frequency for the driving pulse
amplitude = qp.variable(ql.Domain.Voltage)  # Just for the renormalization for the Q1ASM

with qp.average(HW_AVG):
    # Loop over all frequencies
    with qp.for_loop(variable=amplitude, start=AMP_VALUES[0], stop=AMP_VALUES[-1], step=AMP_VALUES[1]-AMP_VALUES[0]):
        qp.set_gain(bus=f"drive_q{QUBIT_IDX}_bus", gain=amplitude)

        # play the drive pulse
        qp.play(bus=f"drive_q{QUBIT_IDX}_bus", waveform=drag)

        # sync the two buses
        qp.sync()

        # wait for some time
        qp.wait(bus=f"readout_bus_{QUBIT_IDX}",duration=BUFFER_TIME)

        # start measuring by playing the readout pulse and only wait for a time_of_flight duration
        qp.play(bus=f"readout_bus_{QUBIT_IDX}", waveform=square_wf, wait_time=time_of_flight)

        # acquire results
        qp.acquire(bus=f"readout_bus_{QUBIT_IDX}", weights=ql.IQPair(I=weights_shape, Q=weights_shape))

        # wait qubit to relax. In the next loop iteration buses will be in sync.
        qp.wait(bus=f"readout_bus_{QUBIT_IDX}",duration=REPETITION_DURATION)
visagim commented 8 months ago

Hi @4dri8

platform_name = "/home/victor/qililab/examples/runcards/galadriel.yml" platform = ql.build_platform(runcard=platform_name)

QProgram used

QUBIT_IDX = 0 AMP_VALUES = np.arange(10,20,1) HW_AVG = 10 qp = ql.QProgram() REPETITION_DURATION = 100

Defining the variable Frequency for the driving pulse

amplitude = qp.variable(ql.Domain.Voltage) # Just for the renormalization for the Q1ASM DRAG_DURATION = platform.get_parameter(parameter = ql.Parameter.DURATION, alias=f"Drag({QUBIT_IDX})") drag = DragPair( amplitude=platform.get_parameter(parameter = ql.Parameter.AMPLITUDE, alias=f"Drag({QUBIT_IDX})"), duration=DRAG_DURATION, num_sigmas=platform.get_parameter(parameter = ql.Parameter.NUM_SIGMAS, alias=f"Drag({QUBIT_IDX})"), drag_coefficient=platform.get_parameter(parameter = ql.Parameter.DRAG_COEFFICIENT, alias=f"Drag({QUBIT_IDX})")) t = platform.get_parameter(parameter=ql.Parameter.DURATION, alias=f"M({QUBIT_IDX})") weights = ql.Square(amplitude=1, duration=t) square_wf = waveform=ql.Square( amplitude=platform.get_parameter(parameter=ql.Parameter.AMPLITUDE, alias=f"M({QUBIT_IDX})"), duration=t)

with qp.average(HW_AVG):

Loop over all frequencies

with qp.for_loop(variable=amplitude, start=AMP_VALUES[0], stop=AMP_VALUES[-1], step=AMP_VALUES[1]-AMP_VALUES[0]):
    qp.set_gain(bus=f"drive_q{QUBIT_IDX}_bus", gain=amplitude)
    qp.wait(bus=f"feedline_bus",duration=REPETITION_DURATION)
    qp.sync()
    qp.play(bus=f"drive_q{QUBIT_IDX}_bus", waveform=drag)
    qp.wait(bus=f"feedline_bus",duration=DRAG_DURATION)
    qp.play(bus=f"feedline_bus", waveform=square_wf) # Executing the Square pulse
    qp.acquire(bus=f"feedline_bus", weights=ql.IQPair(I=weights, Q=weights)) # Measuring

qblox_compiler = QbloxCompiler() sequences = qblox_compiler.compile(qprogram=qp)

for _, sequence in sequences.items(): print(sequence._program)

*note that you don't need to connect to the instruments to run this
This outputs q1asm programs

setup: wait_sync 4

main: move 10, R0
avg_0: move 0, R1
move 0, R2
move 0, R3
move 10, R4
move 327670, R5
loop_0: wait 100
wait 20
play 0, 1, 2000
acquire_weighed 0, R3, R2, R1, 2000 add R3, 1, R3
add R5, 32767, R5
loop R4, @loop_0
loop R0, @avg_0
stop

setup: wait_sync 4

main: move 10, R0
avg_0: move 10, R1
move 327670, R2
loop_0: set_awg_gain R2, R2
wait 100
play 0, 1, 20
wait 4000
add R2, 32767, R2
loop R1, @loop_0
nop
loop R0, @avg_0
stop



tl;dr I dont see the extra 4ns that you're seeing.
Would you mind providing your full code + runcard, as well as the runcard you're running it from so I can try to reproduce it?
rsagas commented 7 months ago

Hi guys, I want to have a meeting about this.

A while ago i spoke about MULTIDEVICE INSTRUCTIONS in QPysequence. It is the only scalable solution I know for this.

I am also very concerned about issues that I have written and have dissapeared (specifically the one relevant now, for multidevice instructions).

visagim commented 7 months ago

Could it be this one? https://github.com/qilimanjaro-tech/qililab/issues/74 Be mindful that compilation is already outside of the drivers (we changed this a few weeks ago) and it already supports compiling sequences for different QRMs / QCMs for the same program (and the extension to clusters should be quite straight forward)

visagim commented 7 months ago

We should definitely have a discussion about this though

rsagas commented 7 months ago

It is not that PR. I searched in the repo, and it is the second time I cannot find an issue on a vision thing I wrote. Do you (team) erase issues ever if you feel they do not point to an action? Cause these ones were preventing issues that are only now coming up.

Anyhow, I do want to discuss the topic, it is about a QPYSEQUENCE improvement.

4dri8 commented 7 months ago

Hi @4dri8

  • Regarding the long wait issue, we are aware of this and have a fix in main by the end of the week. I have tried running your code, adding the definitions of drag, QUBIT_IDX and so on. The full code looks like this for me:
import numpy as np
import qililab as ql
from qililab.qprogram.blocks import Block
from qpysequence.utils.constants import AWG_MAX_GAIN, INST_MAX_WAIT
from qililab.qprogram.operations import *
from qililab.qprogram import QbloxCompiler
from qililab.waveforms import DragPair
import numpy as np

platform_name = "/home/victor/qililab/examples/runcards/galadriel.yml"
platform = ql.build_platform(runcard=platform_name)

#QProgram used

QUBIT_IDX = 0
AMP_VALUES = np.arange(10,20,1)
HW_AVG = 10
qp = ql.QProgram()
REPETITION_DURATION = 100

# Defining the variable Frequency for the driving pulse
amplitude = qp.variable(ql.Domain.Voltage) # Just for the renormalization for the Q1ASM
DRAG_DURATION = platform.get_parameter(parameter = ql.Parameter.DURATION, alias=f"Drag({QUBIT_IDX})")
drag = DragPair(
        amplitude=platform.get_parameter(parameter = ql.Parameter.AMPLITUDE, alias=f"Drag({QUBIT_IDX})"),
        duration=DRAG_DURATION,
        num_sigmas=platform.get_parameter(parameter = ql.Parameter.NUM_SIGMAS, alias=f"Drag({QUBIT_IDX})"),
        drag_coefficient=platform.get_parameter(parameter = ql.Parameter.DRAG_COEFFICIENT, alias=f"Drag({QUBIT_IDX})"))
t = platform.get_parameter(parameter=ql.Parameter.DURATION, alias=f"M({QUBIT_IDX})")
weights = ql.Square(amplitude=1, duration=t)
square_wf = waveform=ql.Square(
        amplitude=platform.get_parameter(parameter=ql.Parameter.AMPLITUDE, alias=f"M({QUBIT_IDX})"),
        duration=t)

with qp.average(HW_AVG):
    # Loop over all frequencies
    with qp.for_loop(variable=amplitude, start=AMP_VALUES[0], stop=AMP_VALUES[-1], step=AMP_VALUES[1]-AMP_VALUES[0]):
        qp.set_gain(bus=f"drive_q{QUBIT_IDX}_bus", gain=amplitude)
        qp.wait(bus=f"feedline_bus",duration=REPETITION_DURATION)
        qp.sync()
        qp.play(bus=f"drive_q{QUBIT_IDX}_bus", waveform=drag)
        qp.wait(bus=f"feedline_bus",duration=DRAG_DURATION)
        qp.play(bus=f"feedline_bus", waveform=square_wf) # Executing the Square pulse
        qp.acquire(bus=f"feedline_bus", weights=ql.IQPair(I=weights, Q=weights)) # Measuring

qblox_compiler = QbloxCompiler()
sequences = qblox_compiler.compile(qprogram=qp)

for _, sequence in sequences.items():
    print(sequence._program)

*note that you don't need to connect to the instruments to run this This outputs q1asm programs

setup:
                wait_sync        4              

main:
                move             10, R0         
avg_0:
                move             0, R1          
                move             0, R2          
                move             0, R3          
                move             10, R4         
                move             327670, R5     
loop_0:
                wait             100            
                wait             20             
                play             0, 1, 2000     
                acquire_weighed  0, R3, R2, R1, 2000
                add              R3, 1, R3      
                add              R5, 32767, R5  
                loop             R4, @loop_0    
                loop             R0, @avg_0     
                stop                            

setup:
                wait_sync        4              

main:
                move             10, R0         
avg_0:
                move             10, R1         
                move             327670, R2     
loop_0:
                set_awg_gain     R2, R2         
                wait             100            
                play             0, 1, 20       
                wait             4000           
                add              R2, 32767, R2  
                loop             R1, @loop_0    
                nop                             
                loop             R0, @avg_0     
                stop

tl;dr I dont see the extra 4ns that you're seeing. Would you mind providing your full code + runcard, as well as the runcard you're running it from so I can try to reproduce it?

There are 16ns of delay from the qcm (there you have your Q1ASM with the timings), in the Q1ASM below you can see the timings of every instruction, in this way: "#absolute time (instruction time)"

setup:
                wait_sync        4                      #4ns (4)

main:
                move             10, R0                 #8ns (4)    
avg_0:
                move             0, R1                  #12ns (4)
                move             0, R2                  #16ns (4)
                move             0, R3                  #20ns (4)
                move             10, R4                 #24ns (4)
                move             327670, R5             #28ns (4)
loop_0:
                wait             100                    #128ns (100)
                wait             20                     #148ns (20)
                play             0, 1, 2000             #152ns (should be 4, already solved)
                acquire_weighed  0, R3, R2, R1, 2000    #2152ns (2000)
                add              R3, 1, R3              #2164ns (12)
                add              R5, 32767, R5          #2176ns (12)
                loop             R4, @loop_0            
                loop             R0, @avg_0     
                stop                            

setup:
                wait_sync        4                      #4ns (4)

main:
                move             10, R0                 #8ns (4)
avg_0:
                move             10, R1                 #12ns (4)
                move             327670, R2             #16ns (4)
loop_0:
                set_awg_gain     R2, R2                 #24ns (8)
                wait             100                    #124ns (100)
                play             0, 1, 20               #144ns (20)
                wait             4000                   #2148ns (should be 4 (qrm play)+2000(qrm acquire))
                add              R2, 32767, R2          #2160ns (12)
                loop             R1, @loop_0    
                nop                             
                loop             R0, @avg_0     
                stop
fedonman commented 7 months ago

Hello @4dri8

The only instructions that add towards timings are play, wait and acquire. These three instructions are run in the "real-time processor" of the sequencer. The rest of the instructions are run in "classical processor". A deeper explanation is given here: https://www.notion.so/qilimanjaro/Q1ASM-timing-d3bb142a450748209e57f2d585fc31f6

So in your example, timings would be:

setup:
                wait_sync        4                      #4ns (4)

main:
                move             10, R0   
avg_0:
                move             0, R1
                move             0, R2
                move             0, R3
                move             10, R4
                move             327670, R5
loop_0:
                wait             100                    #104ns (100)
                wait             20                     #124ns (20)
                play             0, 1, 4                #128ns (should be 4, already solved)
                acquire_weighed  0, R3, R2, R1, 2000    #2128ns (2000)
                add              R3, 1, R3
                add              R5, 32767, R5
                loop             R4, @loop_0            
                loop             R0, @avg_0     
                stop                            

setup:
                wait_sync        4                      #4ns (4)

main:
                move             10, R0
avg_0:
                move             10, R1
                move             327670, R2
loop_0:
                set_awg_gain     R2, R2
                wait             100                    #104ns (100)
                play             0, 1, 20               #124ns (20)
                wait             2004                   #2128ns (should be 4 (qrm play)+2000(qrm acquire))
                add              R2, 32767, R2
                loop             R1, @loop_0    
                nop                             
                loop             R0, @avg_0     
                stop

So at the end the two sequencers are in sync.

4dri8 commented 7 months ago

Then I cannot identify the problem, but I'm not being able to get a rabi with QProgram, even with the code I showed in the opening of the issue, I'm not able to reproduce it.

4dri8 commented 7 months ago

I just found an error in the code I was using, I assumed that, as qililab does, qprogram would assume that, if a parameter is not modified in the current code, it is equal to the value in the runcard, as for example frequencies, but this is not done for the amplitudes of the waveforms. Qililab gives all the waveforms' amplitudes equal to 1 and then these are modified in the Q1ASM to the actual value from the runcard, so I did the same with the readout, as I was not modifying it, I gave the amplitude of the readout as 1, and obviously that is too much power. After solving this, it seems to work fine. Sorry for not seeing this before.

fedonman commented 7 months ago

Glad to hear it worked! I'll mark this as done then, and if anything else arises you can create a new issue.