.. _sequencing: Sequencing ---------- Overview and Usage ^^^^^^^^^^^^^^^^^^ Defining pulses using the :ref:`pulse template definition class structures ` yields a tree structure of :class:`.PulseTemplate` objects. To obtain a concrete pulse that can be executed, parameters have to be replaced by corresponding values and the object structure has to be converted into a sequence of waveforms (and possibly triggered jump annotations) that the hardware drivers can comprehend. This process is called *sequencing* in the qctoolkit. It converts atomic pulse templates like :class:`.TablePulseTemplate` and :class:`.FunctionPulseTemplate` into a waveform representation by sampling voltage values along the time domain using the specified interpolation rules between table values or evaluating the defining function respectively. The tree structure arising from the use of :class:`.SequencePulseTemplate`, :class:`.RepetitionPulseTemplate`, :class:`.ForLoopPulseTemplate`, :class:`.BranchPulseTemplate` and :class:`.LoopPulseTemplate` is converted into an intermediate instruction language consisting of four instructions: Execute a waveform (:class:`.EXECInstruction`), an unconditional goto to another instruction (:class:`.GOTOInstruction`), a conditional jump to another instruction (:class:`.JMPInstruction`) and a stop command, halting execution (:class:`.STOPInstruction`). This approach was inspired by translation of syntax trees to machine instructions in modern compilers and necessitated by the fact that the hardware requires sequential commands rather than convoluted object structures. The output of the sequencing process is a set of waveforms and a sequence of instructions. These will later be interpreted by hardware drivers which will configure the specific devices accordingly (assuming these are capable of the required functionality). If :class:`.BranchPulseTemplate` and :class:`.LoopPulseTemplate` are not used, the compiled instruction sequence will consist only of execution instructions followed by a stop instruction, which represents a simple sequence of waveforms to play back. The sequencing process is performed by a :class:`.Sequencer` and started with a call to :meth:`.Sequencer.build`. :class:`.Sequencer` maintains a stack of :class:`.SequencingElement` objects (i.e. pulse templates) that still need to be translated. Pushing a pulse template to the stack using :meth:`.Sequencer.push` will cause :class:`.Sequencer` to translate it next. Translation works as follows: :class:`.Sequencer` pops the first element from the stack and calls its :meth:`.SequencingElement.build_sequence` method. In the case of a :class:`.TablePulseTemplate`, this adds an :class:`.EXECInstruction` referring to a :class:`.TableWaveform` to the instruction sequence. In the case of a :class:`.SequencePulseTemplate`, this simply adds all subtemplates to the sequencing stack such that they are translated next. :class:`.FunctionPulseTemplate` and :class:`.RepetitionPulseTemplate` act very similarly. :class:`.BranchPulseTemplate` and :class:`.LoopPulseTemplate` behave in a more complicated way due to their branching ability. See the section on implementation details below. The sequencing process can be interrupted at any point, e.g., if some parameter value depends on measurements that are to be made in the preceding part of the pulse. In this case, the method :meth:`.SequencingElement.requires_stop` of the first stack element will return `true`. :class:`.Sequencer` then stops the translation and returns the instruction sequence generated so far. This can then be executed and measurements can be made. Afterwards, the sequencing process can be invoked again. :class:`.Sequencer` will resume where it was interrupted previously (with the first item that remained on the stack). :meth:`.Sequencer.has_finished` allows to check, whether the sequencing process was completed. Sequencing of Conditional Branching ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Software and hardware conditions result in different instruction sequences generated by the sequencing process: Hardware conditions will produce instructions for all possible branches with branching instructions corresponding to the triggers specified by the :class:`.HardwareCondition` instance. The selection of the correct branch is then made by the hardware. Contrary, software conditions will only produce instructions for the branch selected by the condition (the hardware will then never know of potential other execution paths). In this case, no branching instructions will be used. This enables usage of branching even on hardware that does not support jumps with the disadvantage of being not real-time capable (cf. :ref:`conditional branching `.). Implementation Details and Algorithm Walkthrough ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The implementation sequencing algorithm itself is spread over all participating classes. The :class:`.Sequencer` maintains a stack of pulse templates which are to be translated (the *sequencing stack*) and implements the overall control flow of the algorithm: It proceeds to remove the first template from the stack and call :meth:`.PulseTemplate.build_sequence` until either the stack is empty or the first element cannot be translated. Afterwards it embeds all :class:`.InstructionBlock` s into one single :class:`.InstructionSequence` and resolves pointers in :class:`.JMPInstruction` s. Details on why this is necessary will follow below. A crucial component of the sequencing process are the methods :meth:`.PulseTemplate.requires_stop` and :meth:`.PulseTemplate.build_sequence` (defined in the abstract interface :class:`.SequencingElement`). These accept a mapping of parameters and conditions as well as the :class:`.InstructionBlock` that is currently under construction and implement the specific functionality of the corresponding template required in the sequencing process. Examples of this were already briefly stated above but shall be elaborated more in the following. Considering the :class:`.TablePulseTemplate`, the required result of the sequencing process is a waveform representation of the pulse defined by the template and an instruction that this waveform shall be executed by the hardware. If any parameter cannot be evaluated at the time, the sequencing process should stop since the waveform cannot be created. Consequently, :meth:`.TablePulseTemplate.requires_stop` is implemented to return true if any of the parameters required by the template cannot be evaluated, causing the :class:`Sequencer` to interrupt the sequencing process before calling :meth:`.TablePulseTemplate.build_sequence`. :meth:`.TablePulseTemplate.build_sequence`, evaluates the required parameters, creates the waveform and adds a corresponding :class:`.EXECInstruction` to the :class:`.InstructionBlock`. Since conditions are only relevant for branching, the corresponding mapping passed into the method is ignored in the :class:`.TablePulseTemplate` implementation. For a :class:`.SequencePulseTemplate` we require that all templates forming this sequence will be translated, i.e., the translation of the :class:`.SequencePulseTemplate` is the sequential translation of all contained subtemplates. To this end, :meth:`.SequencePulseTemplate.build_sequence` does not perform any translation by itself but simply pushes all subtemplates to the sequencing stack, only taking care to map parameters if necessary. Conditions are ignored as in the case of :class:`.TablePulseTemplate`. Since :class:`.SequencePulseTemplate` does not need to evaluate any parameters by itself, :meth:`.SequencePulseTemplate.requires_stop` always returns false. Finally, for a :class:`.LoopPulseTemplate` we expect one of the following behaviours depending on whether the looping condition is evaluated hardware- or software-based: In the first case, the template representing the loop body should be translated and wrapped with conditional jumping instructions which will cause the hardware device to repeat the resulting waveforms as long as the condition holds. In the second case, the condition must be evaluated by the software and, if it is true, the loop body must be translated and executed in-place, i.e. without any jumps. Afterwards, the execution must stop to reevaluate the condition in the software and decide whether the loop body must be executed again. Since these different behaviours for hardware- and software-based evaluation are similar for loops and branches, they are not directly implemented in the :class:`.LoopPulseTemplate` and :class:`.BranchPulseTemplate` classes but in the corresponding implementations of :class:`.Condition`. The task of :meth:`.LoopPulseTemplate.build_sequence` is thus only to obtain the correct instance of :class:`.Condition` from the provided conditions mapping and delegate the call to :meth:`.Condition.build_sequence_loop`. Depending on whether the condition is a :class:`.HardwareCondition` or :class:`.SoftwareCondition` instance, :meth:`.Condition.build_sequence_loop` will push the required templates to the sequencing stack and generate the corresponding jumping instructions. During the sequencing process, instructions are not directly stored in a fixed sequence. This would be impractical since during the process it is not clear how many alternative paths due to branching and looping will arise and thus where to place the corresponding instructions in a single sequence. Instead, each branch or loop body is represented by an :class:`.InstructionBlock` comprising all execution instructions in this branch as well as potential jump instructions into other blocks. This allows independent construction of blocks during the sequencing process. The :class:`.Sequencer` maintains separate sequencing stacks for each block which allows to continue translating other blocks if a particular block encounters a template which requires a stop. The block into which instructions have to be placed is passed as an argument into :meth:`.PulseTemplate.build_sequence`. With the exception of the main instruction block, which represents the entry point of instruction execution, every block is entered by a conditional jump instruction in another block and ends with an unconditional jump/goto back to the next instruction of that block. When the sequencing process finished, :class:`.Sequencer` embeds all instruction blocks into a single sequence and resolves the instruction pointers of the jump instructions to positions in this sequence instead of references to other blocks. The result is a sequence of instructions represented as :class:`.InstructionSequence` in which no blocks remain and executions paths are solely represented by jumps to instruction positions. For two step-by-step examples on how the sequencing process proceeds, see :ref:`examples/09DetailedSequencingWalkthrough.ipynb`. .. note:: Provide an exemplary sequencing run