qiboteam / qibolab

Quantum hardware module and drivers for Qibo.
https://qibo.science
Apache License 2.0
42 stars 14 forks source link

Sweeper unrolling #1059

Open sorewachigauyo opened 1 month ago

sorewachigauyo commented 1 month ago

Some instruments are unable to use hardware sweepers and can only play a sequence at a time. Also, conventional AWGs are able to store multiple sequences.

To implement the "sweeper functionality" for these devices, I propose an alternative approach to unrolling to unroll the sweepers into an array of sequences. The controller will then either play all the sequences together or one sequence at a time.

alecandido commented 1 month ago

Indeed, this is definitely useful, since it could even greatly simplify Qibocal. Currently, some routines are more or less duplicated, only because there is a _sequences version in order to avoid sweepers usage.

However, in my mind we could go even further, and instead of unrolling the sweepers at drivers' level, we could do this directly in the Platform.

In order to do this, we should implement part of #854, and list in the instrument class the supported sweepers. At that point, the Platform could query its instruments, and it should be able to determine which sweepers can not be executed, and replace them by unrolling[*].

It's not really that simple, only because we may have multiple instruments in a platform, and there is no requirement of having a single Controller. Though in practice we always have just one, in principle we don't want to, since even the pulses might be generated from different devices with a common trigger. So, we should work out a way to tell which instrument would be responsible for which sweeper.

In any case, even assuming a single Controller could be an option, temporarily. And then we could "fail" the fallback if there are multiple ones (so, avoid the unrolling, and pass them the sweepers - in case they're not implemented, just failing).

[*]: it doesn't have to be true unrolling, as this is possibly hard to implement for some drivers, e.g. because you need the readout operation at the end of the experiment; but we're batching the unrolled sequence at Platform level, so it's sufficient to use a Bounds(1, 1, 1) to enforce batches of unit size, but they would still be executed in a single Platform.execute() call - that is what matters the most for users

sorewachigauyo commented 1 month ago

However, in my mind we could go even further, and instead of unrolling the sweepers at drivers' level, we could do this directly in the Platform.

I didn't think of this, but that would indeed help in a multi-instrument setup.

In order to do this, we should implement part of #854, and list in the instrument class the supported sweepers. At that point, the Platform could query its instruments, and it should be able to determine which sweepers can not be executed, and replace them by unrolling[*].

It's not really that simple, only because we may have multiple instruments in a platform, and there is no requirement of having a single Controller. Though in practice we always have just one, in principle we don't want to, since even the pulses might be generated from different devices with a common trigger. So, we should work out a way to tell which instrument would be responsible for which sweeper.

Indeed, we also may need to respect the amount of times we run different instruments. For example, if instrument 1 has a hardware sweeper and instrument 2 does not need to be swept, then we pass the sweeper to instrument 1 and may have to inform instrument 2 that the same sequence is to be played for the sweeper's length.

[*]: it doesn't have to be true unrolling, as this is possibly hard to implement for some drivers, e.g. because you need the readout operation at the end of the experiment; but we're batching the unrolled sequence at Platform level, so it's sufficient to use a Bounds(1, 1, 1) to enforce batches of unit size, but they would still be executed in a single Platform.execute() call - that is what matters the most for users

So my main motivation behind this was the Align operator to ensure that the readout operation is at the end of the experiment.

sweeper = Sweeper(parameter=Parameter.duration, pulses=[rabi_pulse], values=(20, 220, 2))
sequence = [(drive_chan, rabi_pulse), (drive_chan, align1), (acq_chan, align1), (acq_chan, acquisition)]

During my first implementation of python recursive based sweeper handling, I was first using sequence.align_to_delays() before iterating through the pulses, but this does not respect the align operation when the duration is swept. I think that this approach of nested sequences per sweeper, then aligning delays would remedy this issue.

alecandido commented 1 month ago

Indeed, we also may need to respect the amount of times we run different instruments. For example, if instrument 1 has a hardware sweeper and instrument 2 does not need to be swept, then we pass the sweeper to instrument 1 and may have to inform instrument 2 that the same sequence is to be played for the sweeper's length.

Yes, but to do this, the current Qibolab strategy would be to act at the sequence level.

If we unroll, we'll pass the unrolled sequences to all instruments. Otherwise, it will be the sweepers going to all instruments. Then, each driver will filter the part relevant to it, and upload.

It would be hard to deduplicate information on a general basis. Like "this instrument only needs to repeat this sequence irrespective of other portions of the sequence being changing for other instruments", but:

So my main motivation behind this was the Align operator to ensure that the readout operation is at the end of the experiment.

Yes, that's more or less the reason why Align has been introduced.

During my first implementation of python recursive based sweeper handling, I was first using sequence.align_to_delays() before iterating through the pulses, but this does not respect the align operation when the duration is swept. I think that this approach of nested sequences per sweeper, then aligning delays would remedy this issue.

Yeah, the .align_to_delays() is there for compatibility, especially for drivers not natively supporting aligning instructions. If you have align available as primitive, you should avoid compiling to delays, and use the native instruction. Unfortunately, the general align conversion requires acting at runtime in the presence of time sweepers, and this is not possible just operating on sequences. Unraveling the sweeper may really solve your problem, as all the sequences would be explicit at that point, and the delays conversion should succeed.

If you don't have native aligns, consider not supporting duration sweepers in a first iteration of the Keysight driver. Qibocal has alternative experiments for drivers not supporting some sweepers (e.g. rabi_length_sequences). Then, we can separately decide how much prioritizing the implementation of this feature (which should not be terribly complex).