Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pulse scheduling and duration sweepers #935

Closed
stavros11 opened this issue Jul 15, 2024 · 5 comments · Fixed by #979
Closed

Pulse scheduling and duration sweepers #935

stavros11 opened this issue Jul 15, 2024 · 5 comments · Fixed by #979
Assignees
Milestone

Comments

@stavros11
Copy link
Member

This issue has been observed with Quantum Machines but may be relevant for other instruments.

In qibolab 0.2 we provide a Delay object that can be directly translated to wait (or the equivalent) instruction in the driver. While testing this for the Rabi length using the sequence is defined in https://github.com/qiboteam/qibocal/blob/04ee2f6f05d211edf4528731d309222e963064f6/src/qibocal/protocols/rabi/utils.py#L252
in which we sweep the duration of the drive pulse and the delay before readout at the same time (to make sure readout is pushed after drive ends), I realized that it does not work as well with the new translation, as shown in the following reports:

Checking the generated QUA programs, I noticed that 0.1 translates to:

with program() as prog:
    v1 = declare(int, )
    v2 = declare(fixed, )
    v3 = declare(fixed, )
    v4 = declare(int, )
    v5 = declare(int, )
    wait((4+(0*((Cast.to_int(v2)+Cast.to_int(v3))+Cast.to_int(v4)))), "readoutD1")
    with for_(v1,0,(v1<2000),(v1+1)):
        with for_(v5,4,(v5<=49),(v5+1)):
            align()
            play("drive(16, 0.042, Gaussian(5))", "driveD1", duration=v5)
            align("readoutD1", "driveD1")
            measure("readout(2000, 0.0015, Rectangular())", "readoutD1", None, dual_demod.full("cos", "out1", "sin", "out2", v2), dual_demod.full("minus_sin", "out1", "cos", "out2", v3))
            assign(v4, Cast.to_int((((v2*0.9758945802934583)-(v3*-0.21824245268474865))>0.003026542294327842)))
            r1 = declare_stream()
            save(v4, r1)
            wait(25000, )
    with stream_processing():
        r1.buffer(46).buffer(2000).save("readout(2000, 0.0015, Rectangular())_D1_shots")

while 0.2 to:

with program() as prog:
    v1 = declare(int, )
    v2 = declare(fixed, )
    v3 = declare(fixed, )
    v4 = declare(int, )
    v5 = declare(int, )
    wait((4+(0*((Cast.to_int(v2)+Cast.to_int(v3))+Cast.to_int(v4)))), "readoutD1")
    with for_(v1,0,(v1<2000),(v1+1)):
        with for_(v5,4,(v5<=49),(v5+1)):
            align()
            play("duration=40.0 amplitude=0.042 envelope=Gaussian(kind='gaussian', rel_sigma=0.2) relative_phase=0.0 type=<PulseType.DRIVE: 'qd'>", "driveD1", duration=v5)
            wait(v5, "readoutD1")
            measure("duration=2000.0 amplitude=0.0015 envelope=Rectangular(kind='rectangular') relative_phase=0.0 type=<PulseType.READOUT: 'ro'>", "readoutD1", None, dual_demod.full("cos", "out1", "sin", "out2", v2), dual_demod.full("minus_sin", "out1", "cos", "out2", v3))
            assign(v4, Cast.to_int((((v2*0.9758945802934583)-(v3*-0.21824245268474865))>0.003026542294327842)))
            r1 = declare_stream()
            save(v4, r1)
            wait(25000, )
    with stream_processing():
        r1.buffer(46).buffer(2000).save("duration=2000.0 amplitude=0.0015 envelope=Rectangular(kind='rectangular') relative_phase=0.0 type=<PulseType.READOUT: 'ro'>_shots")

In 0.2 we are translating Delay to wait as expected, while in 0.1 the driver forces readout to be after drive using align. The latter seems to work better for that particular routine, as shown in the reports.

I am guessing when using wait with exactly the duration of the drive pulse, there may still be some overlap between drive and readout that affects results, particularly for short durations. A potential solution (not tested) is to make the driver introduce small delays on its own in such cases, however that means that the sequence we are playing is not exactly the one defined by the user. An alternative is to translate exactly the given sequence and let the user (in this case qibocal) add more delays if such issues appear.

@alecandido
Copy link
Member

Why the 0.2 version is still using the serials? They are even worse than the 0.1 equivalents...

However, apart for the serials, the diff I'm getting is:
image
left 0.2, right 0.1 (I tried uploading text, but colors were relevant...)

From this, I get that the only relevant difference is indeed the wait() vs align() (lines 28l 24r).

Since we would naively expect them to be the same (and most likely the problem consists in missing baking, or something like that), could we use the simulator to get waveforms plots?
https://docs.quantum-machines.co/1.1.7/qm-qua-sdk/docs/Guides/simulator/?h=simulator#to-visualize-the-waveform-report

@stavros11
Copy link
Member Author

Why the 0.2 version is still using the serials? They are even worse than the 0.1 equivalents...

The program that is sent to the device is not using the serials, it is using the hash, as defined in

def operation(pulse):
"""Generate operation name in QM ``config`` for the given pulse."""
return str(hash(pulse))

The programs I posted here are the ones I am dumping to disk for debugging and there I replace the hash with a more human readable description:

script = script.replace(operation(pulse), str(pulse))

I guess here str(pulse) uses some default pydantic serialization. However, this is only there for debugging and it not meant to be used in "production".

Since we would naively expect them to be the same (and most likely the problem consists in missing baking, or something like that), could we use the simulator to get waveforms plots? https://docs.quantum-machines.co/1.1.7/qm-qua-sdk/docs/Guides/simulator/?h=simulator#to-visualize-the-waveform-report

Here are the results of the simulator (zoomed in the relevant area):

0.1

Screenshot 2024-07-16 221355

0.2

Screenshot 2024-07-16 221425

Actually 0.2 is worse than I expected and the whole drive is pushed inside the readout. That explains the bad results.

I don't think the issue is related to baking, as I am only using durations that are multiple of 4ns in this example, so baking should not be needed. This is usually sufficient for single qubit Rabi anyway. However, I think the issue is that I am using the instrument interpolation to change the pulse duration real time by passing the duration argument in play (see https://docs.quantum-machines.co/1.1.7/qm-qua-sdk/docs/Guides/features/?h=interpolat#dynamic-pulse-duration) and this probably introduces some delay in the corresponding channel. See first bullet point here: https://docs.quantum-machines.co/1.1.7/qm-qua-sdk/docs/Guides/best_practices/
Using align probably accounts for such delays, because I guess they are known during compilation, while wait only uses whatever time we pass and we are not taking into account other delays.

If you notice in 0.1 there is even a gap of ~20ns between drive and readout. I have noticed align introducing such gaps in other examples with the simulator in the past.

@alecandido
Copy link
Member

I guess here str(pulse) uses some default pydantic serialization. However, this is only there for debugging and it not meant to be used in "production".

Ok, this reminds me I should reprioritize also #773. But maybe after #687.

Actually 0.2 is worse than I expected and the whole drive is pushed inside the readout. That explains the bad results.

Yes, I would say that is much worse (than I expected)...

I don't think the issue is related to baking, as I am only using durations that are multiple of 4ns in this example, so baking should not be needed.

I thought it could have been some small details, but it seems macroscopic. It is definitely not baking. And yes, in the scripts the numbers were only in the serialization, but I'm reading now that they are all multiple of 4, so I could have excluded myself.

However, I think the issue is that I am using the instrument interpolation to change the pulse duration real time by passing the duration argument in play (see docs.quantum-machines.co/1.1.7/qm-qua-sdk/docs/Guides/features?h=interpolat) and this probably introduces some delay in the corresponding channel. See first bullet point here: docs.quantum-machines.co/1.1.7/qm-qua-sdk/docs/Guides/best_practices
Using align probably accounts for such delays, because I guess they are known during compilation, while wait only uses whatever time we pass and we are not taking into account other delays.
If you notice in 0.1 there is even a gap of ~20ns between drive and readout. I have noticed align introducing such gaps in other examples with the simulator in the past.

While align could be a very useful feature to expose (especially for 0.3 and #917), in general it requires threads synchronization, and it causes delay. As you're observing (though I'm not sure that's the reason, of course).

Instead, wait() is much more reliable, if we can control all the other timings. And in our context of pre-compiled sequences, we should be able.

So, maybe it's worth to make some more effort in eliminating the usage of duration parameter, in favor of compile-time duration specifications (most likely within configurations), and keep using wait. It should even reduce these gaps, giving us a finer control on the instrument.

It's not going to be for free, since we would abandon interpolation, thus we might have to upload more waveforms. But I'd be glad to pay this price, since interpolation is also reliable up to a point, so you want to use it explicitly (and completely unneeded for rectangular pulses and delays).

@alecandido
Copy link
Member

This is related to:

though it is the specific QM instance of the phenomenon, including some QM-specific optimization.

However, my solution proposal stems from the observation that the observed distortion in Rabi is generated from QM additional runtime delays, generated by the interpolation of the pulses.
This is a great optimization, but available almost exclusively on QM.
For this reason, in the perspective of #854, we should support this as optional, and introduce even in QM the default duration sweeper, made by multiple waveforms upload.

Thus, as practical steps, I'd propose to:

  • implement duration sweepers in QM with multiple waveforms
  • checking the timing is predictable, and it could sweep a Rabi with just delays wait() instructions
  • introduce a new DurationInterpolated sweeper parameter
  • introduce an Align pulse, to synchronize multiple channels
    • this will have the specific meaning of a runtime synchronization, possibly introducing delays
  • keep Sequence as a list, in such a way that is clear for all channels what it comes before and after an Align

The idea is then that a Rabi length experiment will be implemented with just a Duration sweeper and Delay instructions. Instead, a memory-optimized version of Rabi length could be implemented replacing Duration with DurationInterpolated, and inserting the Align instruction.
Drivers that do not support the optimizations should just explicitly fail (not falling back).
The optimization could also be exposed as a toggle in Rabi length parameters (the code to switch should be minimal).

(of course Rabi length is just an example, but it should apply in a very analogue way to all duration experiments)

@stavros11
Copy link
Member Author

I would consider this as fixed by #979, where we implemented the plan outlined in the comment above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants