{ "cells": [ { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "# Detailed Sequencing Walkthrough\n", "\n", "This example will provide two step-by-step illustrations of the internals of the sequencing process. Note that this will involve some calls into the object structure to unveil internals which are not intended to be made in a productive use case and produce some very technical outputs. These are broken down and explained in detail where necessary.\n", "\n", "## Example 1 (Software-Evaluated Loop Condition)\n", "\n", "In the first example, we will emulate the behaviour of a `RepetitonPulseTemplate` to repeats a `TablePulseTemplate` for a fixed number of times using a `LoopPulseTemplate` with a `SoftwareCondition`. We have done so already in [the example for conditional execution](06ConditionalExecution.ipynb) but here we will explore the sequencing process in detail. The definitions of the classes are the following (resulting in 2 repetitions):" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from qctoolkit.pulses import LoopPulseTemplate, TablePulseTemplate, SoftwareCondition, Sequencer\n", "\n", "# define a table pulse template which we want to repeat (including a parameter)\n", "table_template = TablePulseTemplate()\n", "table_template.add_entry(1, 'foo', 'linear')\n", "table_template.add_entry(3, 'foo')\n", "table_template.add_entry(4, 0, 'linear')\n", "\n", "# define a software condition will evaluate to true as long as the loop counter is less than 5 and false afterwards\n", "repeat_condition = SoftwareCondition(lambda x: x < 2) # it will never require an interruption of the sequencing process\n", "\n", "# define a loop template consisting of the table template as body and a condition identified by 'rcon'\n", "loop_template = LoopPulseTemplate('rcon', table_template)\n", "\n", "# provide sequencing mappings: condition 'rcon' -> repeat_condition and parameter 'foo' -> 2\n", "conditions = {'rcon': repeat_condition}\n", "parameters = {'foo': 2}\n", "\n", "# create a Sequencer instance and push our loop template on the sequencing stack with the corresponding mappings \n", "s = Sequencer()\n", "s.push(loop_template, parameters, conditions)\n", "\n", "# store references to the main instruction block and the corresponding sequencing stack\n", "main_block = s._Sequencer__main_block\n", "sequencing_stack = s._Sequencer__sequencing_stacks[main_block]" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[(, {'foo': }, {'rcon': })]\n" ] } ], "source": [ "print(sequencing_stack) # print the current sequencing stack for the main block" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As you can see in the dump of the sequencing stack of the main instruction block, there is currently one item on the stack, which a tuple consisting of our `LoopPulseTemplate` `loop_template` and the mappings `parameters` and `conditions`. The following figure illustrates the current content sequencing stack.\n", "\n", "![The sequencing stack after pushing `loop_template`](img/walkthrough1_01.png)\n", "\n", "Running `Sequencer.build()` would run the entire sequencing process, resulting in the desired instruction sequence. However, since we want to understand the process itself, we will perform the necessary steps ourselves by manually calling the corresponding functions. We now translate the topmost (and only) stack item:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# get the topmost item from the sequencing stack\n", "(template, params, conds) = sequencing_stack[-1]\n", "# remove template from stack and translate it it does not require a stop\n", "if not template.requires_stop(params, conds):\n", " sequencing_stack.pop()\n", " template.build_sequence(s, params, conds, main_block)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `build_sequence` method looks up the condition identified by 'rcon' in the conditions map `conditions` which is our `repeat_condition` object define dabove. It then invokes the `build_sequence_loop` method of this object. Being a `SoftwareCondition` object, it evaluates its evaluation function, which returns true, and thus adds the body, our `table_template` to the sequencing stack. Since the loop condition must be evaluated again after the loop body was run, also the `loop_template` is pushed to the stack again. Thus, the stack now looks as follows:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[(, {'foo': }, {'rcon': }), (, {'foo': }, {'rcon': })]\n" ] } ], "source": [ "print(sequencing_stack) # print the current sequencing stack for the main block" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "![The sequencing stack after translating `loop_template`](img/walkthrough1_02.png)\n", "\n", "Note that no waveforms or instructions have been generated so far, i.e., the main instruction block is empty:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[]\n" ] } ], "source": [ "print(main_block._InstructionBlock__instruction_list) # print all instructions in the main block" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`Sequencer` would now enter the next iteration, i.e., pop and translate the topmost element from the stack." ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# get the topmost item from the sequencing stack\n", "(template, params, conds) = sequencing_stack[-1]\n", "# remove template from stack and translate it it does not require a stop\n", "if not template.requires_stop(params, conds):\n", " sequencing_stack.pop()\n", " template.build_sequence(s, params, conds, main_block)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This time, our `table_template`, that is, the body of the loop, is at the top. It's translation via `build_sequence()` looks up the parameter value for 'foo', generates a waveform and inserts a corresponding instruction in the main block:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[]\n" ] } ], "source": [ "print(main_block._InstructionBlock__instruction_list) # print all instructions in the main block" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Since we've successfully processed the `table_template` item on the sequencing stack, we are left with a `loop_template` item. That means, the stack looks just like in the beginning (refer to Figure 1)." ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[(, {'foo': }, {'rcon': })]\n" ] } ], "source": [ "print(sequencing_stack)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will fetch it from the stack and translate it. Since the loop counter in the `SoftwareCondition` object is currently 1, it will still evaluate to true, meaning that the loop continues, i.e., the body template and the loop template are again pushed to the stack (cf. Figure 2)." ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[(, {'foo': }, {'rcon': }), (, {'foo': }, {'rcon': })]\n" ] } ], "source": [ "# get the topmost item from the sequencing stack\n", "(template, params, conds) = sequencing_stack[-1]\n", "# remove template from stack and translate it it does not require a stop\n", "if not template.requires_stop(params, conds):\n", " sequencing_stack.pop()\n", " template.build_sequence(s, params, conds, main_block)\n", " \n", "print(sequencing_stack)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Proceeding as before, we translate the topmost element, which is again the loop body `table_template`. This results in the expected `EXECInstruction` and a stack in which the `loop_template` remains for reevaluation." ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[(, {'foo': }, {'rcon': })]\n" ] } ], "source": [ "# get the topmost item from the sequencing stack\n", "(template, params, conds) = sequencing_stack[-1]\n", "# remove template from stack and translate it it does not require a stop\n", "if not template.requires_stop(params, conds):\n", " sequencing_stack.pop()\n", " template.build_sequence(s, params, conds, main_block)\n", " \n", "print(sequencing_stack)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Our main instruction block now contains two `EXECInstruction`s:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[, ]\n" ] } ], "source": [ "print(main_block._InstructionBlock__instruction_list) # print all instructions in the main block" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "We are left with the `loop_template` on the stack, which we will translate in the following. However, this time the `repeat_condition` will evaluate to false, meaning that neither body nor loop template are pushed to the stack. We are done with the loop." ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[]\n" ] } ], "source": [ "# get the topmost item from the sequencing stack\n", "(template, params, conds) = sequencing_stack[-1]\n", "# remove template from stack and translate it it does not require a stop\n", "if not template.requires_stop(params, conds):\n", " sequencing_stack.pop()\n", " template.build_sequence(s, params, conds, main_block)\n", " \n", "print(sequencing_stack)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that we have not yet obtained an `InstructionSequence` but only constructed the main `InstructionBlock` object. We will conclude the sequencing by converting the main block into the desired sequence:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[, , ]\n" ] } ], "source": [ "instruction_sequence = main_block.compile_sequence()\n", "print(instruction_sequence)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this case, this is a trivial task, as it simply takes both instructions contains in the main block and adds a `STOPInstruction` to generate the sequence. Now we are done." ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "True\n" ] } ], "source": [ "print(s.has_finished()) # are we done?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We have explored what happens internally when we invoke `Sequencer.build()` on our `loop_template`. In a productive use case, we can let `Sequencer` handle all of this and get the same result (apart from memory addresses of the involved python objects):" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[, , ]\n" ] } ], "source": [ "s = Sequencer()\n", "repeat_condition = SoftwareCondition(lambda x: x < 2) # it will never require an interruption of the sequencing process\n", "conditions = {'rcon': repeat_condition}\n", "s.push(loop_template, parameters, conditions)\n", "instruction_sequence = s.build()\n", "print(instruction_sequence)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Example 2 (Hardware Evaluated Branch Nested In Loop)\n", "\n", "In this example we want to look into hardware-based branching evaluation based using the `HardwareCondition` class and how `InstructionBlocks` are created and handled during the `Sequencing` process. The pulse we want to translate is a loop which contains a branch template (if-else-construct) which in turn contains table pulse templates:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from qctoolkit.pulses import LoopPulseTemplate, BranchPulseTemplate, TablePulseTemplate, HardwareCondition, Sequencer\n", "from qctoolkit.pulses import Trigger\n", "\n", "# two table pulse templates for the alternative paths of the branch pulse template\n", "# they differ in their interpolation behaviour (jump vs linear ramp)\n", "pos_template = TablePulseTemplate()\n", "pos_template.add_entry(1, 'foo', 'linear')\n", "pos_template.add_entry(3, 'foo')\n", "pos_template.add_entry(4, 0, 'linear')\n", "\n", "neg_template = TablePulseTemplate()\n", "neg_template.add_entry(1, 'foo')\n", "neg_template.add_entry(3, 'foo')\n", "neg_template.add_entry(4, 0)\n", "\n", "parameters = {'foo': 2}\n", "\n", "# the branch pulse template\n", "branch_template = BranchPulseTemplate('bcon', pos_template, neg_template)\n", "\n", "# the loop pulse template\n", "loop_template = LoopPulseTemplate('lcon', branch_template)\n", "\n", "\n", "# for this example: Introduce a trigger that can be identified by a name\n", "class NamedTrigger(Trigger):\n", " def __init__(self, name: str) -> None:\n", " self.name = name\n", " \n", " def __str__(self) -> str:\n", " return \"Trigger '{}'\".format(self.name)\n", "\n", "# create HardwareCondition objects for branch and loop\n", "branch_condition = HardwareCondition(NamedTrigger(\"branch_trigger\"))\n", "loop_condition = HardwareCondition(NamedTrigger(\"loop_trigger\"))\n", "\n", "# mapping of identifiers to conditions\n", "conditions = {'bcon': branch_condition, 'lcon': loop_condition}\n", "\n", "# create a Sequencer instance and push our loop template on the sequencing stack with the corresponding mappings \n", "s = Sequencer()\n", "s.push(loop_template, parameters, conditions)\n", "\n", "# store references to the main instruction block and the corresponding sequencing stack\n", "main_block = s._Sequencer__main_block\n", "main_sequencing_stack = s._Sequencer__sequencing_stacks[main_block]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The sequencing stack now contains a single entry, namely the tuple containing our 'loop_template' and the mappings 'parameters' and 'conditions':" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[(, {'foo': }, {'bcon': , 'lcon': })]\n" ] } ], "source": [ "print(main_sequencing_stack)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "![The initial sequencing stack for example 2](img/walkthrough2_01.png)\n", "\n", "Entering the sequencing process, we translate the topmost element as before:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[]\n" ] } ], "source": [ "# get the topmost item from the sequencing stack\n", "(template, params, conds) = main_sequencing_stack[-1]\n", "# remove template from stack and translate it it does not require a stop\n", "if not template.requires_stop(params, conds):\n", " main_sequencing_stack.pop()\n", " template.build_sequence(s, params, conds, main_block)\n", " \n", "print(main_sequencing_stack)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Surprisingly at a first glance, the sequencing stack of the main instruction block is empty afterwards although we are far from being done with the sequencing process. What happended here is that the call to `LoopPulseTemplate`s `build_sequence()` method resulted in a call to `build_sequence_loop` of the corresponding condition object `loop_condition`. This is of type `HardwareConditon`, meaning that all possible execution paths must be translated into a hardware understandable format. Thus, a new `InstructionBlock` was instantiated into which the body of the loop will be sequenced. Accordingly, the remaining templates which represent the loops body are pushed to the specific sequencing stack of this new instruction block. In the main block we will simply find a `CJMPInstruction` (conditional jump instruction) to the new block." ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[]\n" ] } ], "source": [ "print(main_block._InstructionBlock__instruction_list)" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# obtain a reference to the new InstructionBlock representing the body of the loop\n", "loop_body_block = main_block._InstructionBlock__instruction_list[0].target.block\n", "loop_body_stack = s._Sequencer__sequencing_stacks[loop_body_block]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The contents of the sequencing stacks are the following:" ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[(, {'foo': }, {'bcon': , 'lcon': })]\n" ] } ], "source": [ "print(loop_body_stack) # print the sequencing stack for the loop body block" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "![Sequencing stacks after translating the loop template](img/walkthrough2_02.png)\n", "\n", "`Sequencer` continues the sequencing process, until it cannot proceed for any instruction block currently under construction. Thus, although the stack for the main block is empty, we continue with the loop body block:" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[]\n" ] } ], "source": [ "# get the topmost item from the sequencing stack\n", "(template, params, conds) = loop_body_stack[-1]\n", "# remove template from stack and translate it it does not require a stop\n", "if not template.requires_stop(params, conds):\n", " loop_body_stack.pop()\n", " template.build_sequence(s, params, conds, loop_body_block)\n", " \n", "print(loop_body_stack)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Since we translated a `BranchLoopTemplate` with a `HardwareConditon` we end up with two new instructions blocks, one for the if-branch and one for the else-branch, with separate sequencing stacks. We also obtain corresponding jump instructions in the loop body block: A conditional jump into the if-branch, performed if the condition is fulfulled followed by an unconditional goto into the else-branch, if the conditional jump does not occur, i.e., the condition is not fullfilled." ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[, ]\n" ] } ], "source": [ "print(loop_body_block._InstructionBlock__instruction_list)" ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# get references to if and else branches\n", "if_branch_block = loop_body_block._InstructionBlock__instruction_list[0].target.block\n", "else_branch_block = loop_body_block._InstructionBlock__instruction_list[1].target.block\n", "if_branch_stack = s._Sequencer__sequencing_stacks[if_branch_block]\n", "else_branch_stack = s._Sequencer__sequencing_stacks[else_branch_block]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The stacks now look as follows:\n", "\n", "![Sequencing stacks after translating the branch template](img/walkthrough2_03.png)\n", "\n", "The table pulse templates `pos_template` and `neg_template` are translated in the usual manner, resulting in an `EXECInstruction` in the respective instruction blocks:" ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# translate if-branch stack\n", "(template, params, conds) = if_branch_stack[-1]\n", "if not template.requires_stop(params, conds):\n", " if_branch_stack.pop()\n", " template.build_sequence(s, params, conds, if_branch_block)\n", " \n", "# translate else-branch stack\n", "(template, params, conds) = else_branch_stack[-1]\n", "if not template.requires_stop(params, conds):\n", " else_branch_stack.pop()\n", " template.build_sequence(s, params, conds, else_branch_block)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Afterwards, all stacks are empty" ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[]\n", "[]\n", "[]\n", "[]\n" ] } ], "source": [ "print(main_sequencing_stack)\n", "print(loop_body_stack)\n", "print(if_branch_stack)\n", "print(else_branch_stack)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "and we are left with four instruction blocks, two of which contains `EXECInstructions` while the rest only specifies control flow, that is, (conditional) jumps into other blocks. In an illustration, the blocks look like this:\n", "\n", "![Interconnected instruction block](img/walkthrough2_04.png)\n", "\n", "Note that the returning unconditional gotos are not listed explicitely in the outputs above but always implicitely defined.\n", "\n", "In the final step of the sequencing process, these blocks are now converted into a single sequence of instructions by embedding each blocks instructions into the instructions of the block jumping into it and then adapting the jump pointers. The final sequence is as follows:" ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[, , , , , , , , ]\n" ] } ], "source": [ "instruction_sequence = main_block.compile_sequence()\n", "print(instruction_sequence)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "![Resulting instruction sequence](img/walkthrough2_05.png)\n", "\n", "This instruction sequence indeed represents our desired behavior." ] }, { "cell_type": "code", "execution_count": 28, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "True\n" ] } ], "source": [ "print(s.has_finished()) # really done?" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.4.4" } }, "nbformat": 4, "nbformat_minor": 0 }