Timing in QUA¶
A QUA program defines not only the pulses that are played, but also specifies when they should be played.
Pulse Dependency¶
The QUA syntax defines an implicit pulse dependency, which determines the order of pulses execution. The dependency can be summarized as follows:
- Each pulse is played immediately, unless specified otherwise or a calculation has to be done.
- We can delay the pulse play by using a
wait()
command. Dependency-wise, this is equivalent to a play with zero amplitude. - Pulses applied to the same element are dependent on each other according to the order in which they appear in the program.
- We can create a dependency between different elements via the
align()
command. Whenever elements are 'aligned', the execution of operations that come after the alignment is dependent on the operations that come before.
Note
Each element has its own thread, unless defined otherwise. If elements do share threads, they are dependent on each other as in case 3 above.
The Align Command¶
To further illustrate point number 4 above, consider the following pseudo-code examples:
with program() as prog:
## operations on element_1
align(element_1, element_2)
## operations on element_2
In this example, the operations on element_2
will only begin execution after the last operation on element_1
took place (before the alignment).
Let's consider a slightly more complex example:
with program() as prog:
## operations on element_1
## operations on element_2
## operations on element_3
align(element_1, element_2)
## operations on element_1
## operations on element_2
## operations on element_3
In the first part of the program above, before the alignment, the operations on the different elements will occur simultaneously,
according to the dependency rules above. In the second half, after the alignment, the operations on element_1
& element_2
will only begin after the operations from the first half, on both elements, has finished.
Operations on element_3
are independent of the other elements and will ignore the align command.
The Implicit Align¶
There is an implicit align whenever several elements participate in a flow control branch. That means that when elements appear together in a for loop, while loop, if / else / elif block or a switch case, they are aligned in the beginning of the flow control branch.
For example, the following example:
with program() as prog:
## operations on element_1
with while_(some_condition):
## operations on element_1
## operations on element_2
To the compiler, it is actually:
with program() as prog:
## operations on element_1
align(element_1, element_2)
with while_(some_condition):
align(element_1, element_2)
## operations on element_1
## operations on element_2
In the program, the while loop will only start after the pre-loop operations on element_1
has finished.
In addition, every iteration will begin only after the operations on both element have ended.
Therefore, the loops duration will be determined by the longest of the two operation sequences.
It is possible to add the flag skip-add-implicit-align
to the execution to remove the automatic addition of these aligns.
For more information on how to use flags, see compilation options.
Gaps in QUA¶
The QUA syntax of pulses and operations only defines their order of execution, it does not guarantee that they would be played without introducing gaps.
This means that two consecutive play
commands on the same element may have a short gap between them.
The compiler tries to minimize these gaps, but it is not always possible to completely eliminate them. For tips on how to reduce these gaps, see QUA Best Practice Guide.
It is also possible to indicate to the compiler sections in which it is critical to have no gaps, this can be done with the strict_timing_()
block.
Strict Timing¶
Any command written inside a strict_timing_()
block will be required to be played without gaps.
In cases where this is not possible, an error will be raised indicating the gaps.
It is possible to add the flag not-strict-timing
to the execution to raise warnings instead of errors.
For more information on how to use flags, see compilation options.
In the following example, the two pulses will happen with no gaps in between:
In the next example, the for_()
loop is also inside the strict timing, which also requires that there will be no gaps between different iterations of the loop.
i.e., this means that the following code will produce a chain of 200 pulses alternating between x180
and y90
with no gaps at all.
Deterministic Vs. non-Deterministic Align¶
Generally, an align command requires the passage of information from one thread to the other. When a thread reaches its last instruction before an alignment, it sends a signal to all the other threads that participate in the alignment, also known as a "hardware sync". This passage of information takes several clock cycles and introduces gaps to the pulse sequence. In cases where the duration of the operation on all the elements is known during compilation (Deterministic case), the compiler optimizes the sequence and replaces the hardware sync with precalculated wait commands for each element. This optimization ensures that there are no gaps formed in a deterministic case. However, if the run-time of one of the elements is not known during compilation (non-Deterministic case), gaps will be formed. See examples 3 and 4 below for more details.
Examples for Timing scenarios in QUA¶
We will present five examples in order to demonstrate the timing in qua:
- Two pulses from different elements.
- Two pulses from different elements with wait command.
- Two pulses from different elements with align command (deterministic case).
- Two pulses from different elements with align command (non-deterministic case).
- Two pulses from the same element.
Note
For the executable examples look at timing tutorial.
Example #1¶
Two pulses from different elements.
The two pulses start at the same time since they relate to different elements.
Example #2¶
Two pulses from different elements with wait command.
The play on the resonator is delayed by a 400 ns (100 cycles).
Example #3¶
Two pulses from different elements with align command (deterministic case)
The play on the resonator starts right after the qubit's play ends. This is due to the align command (in the deterministic case).
Note
In the deterministic case above, the align command is translated to the wait command with the specific known wait time.
Example #4¶
Two pulses from different elements with align command (non-deterministic case)
with program() as prog:
# some calculation of t in real time
play('cw1','qubit', duration=t)
align('qubit', 'resonator')
play('cw2', 'resonator')
While t
is calculated in real-time.
If we zoom in to the area where one element ends and the other starts, we can see a gap between them.
Note
When playing two elements with align command in the non-deterministic case, there is a few cycles delay between the pulses. Since in the non-deterministic case it takes few cycles to pass the information from one thread to the other.
Example #5¶
Two pulses from the same element.
The two play commands are played one after the other since they are played from the same element.